﻿
//Developer:    Colin Gardner
//Contact:      cgardner99@gmail.com
//Blog:         http://www.techardinal/com
//Release:      First public beta release 29 Dec 2009
//Notes:        This release depends on the Dec 2009 CTP Release of the Open XML SDK.
//              The solution supports some enhanced capabilities which allow for example the import 
//              of PowerPoint slides and Excel charts as individually selectable images.  The generation
//              of these images is done by another feature solution which relies on a suite of 3rd party
//              controls which provide capabilities not yet available in the SDK.  The solution has been
//              designed not to be dependent on the existence of these 3rd party components but has been
//              engineered to take advantages of that capability should it be available.  For that reason
//              there are some controls and code in this solution which are there just to support this
//              enhanced capability

#region Namespace Declarations

//Quite a lot of them

using System;
using System.IO;
using System.Text;
using System.Linq;
using System.Reflection;

using System.Drawing;
using System.Drawing.Imaging;

using System.Collections;
using System.Collections.Generic;

using System.Xml;
using System.Xml.XPath;

using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Utilities;

using Microsoft.Win32;

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Wordprocessing;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Presentation;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Drawing.Wordprocessing;
using DocumentFormat.OpenXml.Drawing;
using DocumentFormat.OpenXml.Drawing.Pictures;
using V = DocumentFormat.OpenXml.Vml;
using Ovml = DocumentFormat.OpenXml.Vml.Office;

#endregion

#region Enumeration Type Definitions

//Just some standard enum tyep definitions which are useful for storing and processing user selected values
//for things such as import settings etc.

public enum ImportOptions
{
    Unassigned, Chunk, Object, Link, Thumbnail,
    SmallImage, LargeImage, SmallSlides, LargeSlides, Notes,
    SmallSlidesWithNotes, LargeSlidesWithNotes, Tables, TablesAndCharts,
    SheetsAndChartsAsLargeImages, SheetsAndChartsAsSmallImages
};

public enum ImageOptions { All, Selected };

public enum LinkOptions { Filename, Title, Url, Custom };

public enum ImportObjectAs { Object, Package };

#endregion

//This is the code behind class for the application page DocFusion.aspx
public partial class DocFusion : LayoutsPageBase
{

    #region Constants
    //References to some useful graphics thoughtfully provided by SharePoint
    const string cArrupa = "/_layouts/images/arrupa.gif";
    const string cArrupi = "/_layouts/images/arrupi.gif";
    const string cArrdowna = "/_layouts/images/arrdowna.gif";
    const string cArrdowni = "/_layouts/images/arrdowni.gif";
    const string cSelectedSectionUrl = "/_layouts/images/arrowright_light.gif";

    //Opening and Closing tags to encase the text in a treenodeto indicate that it is selected and in focus 
    const string cHighLightOpenTags = "<b><font color='red'>";
    const string cHighLightCloseTags = "</font></b>";

    //Just some useful text strings
    const string cWarningNoSectionsFound = "There are no import points found within the target document. " +
        "To setup import points you must edit the source document and add plain text content controls which " +
        "start with an * symbol e.g. *Annex A.";
    const string cDefaultLinkText = "<Custom Link Text>";

    //Some standard size constants in English Metric Units (EMUs)
    const Int64 cStandardPageWidth = 5731510;  //Standard width of an A4 page
    const Int64 cStandardThumbWidth = 2000000; //A reasonable size for a thumbnail image

    //Limits the length of treenode text 
    const Int16 cMaxNodeTextLength = 40;

    #endregion

    #region Private Class Variables

    //These are initialised on first page load and can then be referenced globally
    private string ListID;
    private string ItemUrl;
    private string ItemID;
    private string WebUrl;
    private string ReferrerUrl;
    private SPFile File;
    private string FileUrl;
    private SPListItem MasterDocItem;
    private SPList List;
    private SPView CurrentView;
    
    #endregion

    #region Application Page Controls

        //These controls are defined in the various areas of the application page
        //and we need to surface them here in order to attach business logic to
        //event handlers etc.

        #region PlaceHolderTitleBreadcrumb

        //These are the simulated breadcrumb links back to the library and web
        protected HyperLink lnkLib;
        protected HyperLink lnkWeb;

        #endregion

        #region PlaceHolderPageTitleInTitleArea

        //This holds the page title text which is set to the file name of the 
        //master document template
        protected Label labPageTitleInTitleArea;

        #endregion

        #region PlaceHolderMain
        //All these controls are in the main content area of the page

        //Labels whose text needs to change dynamically
        protected Label labWarning;
        protected Label labOptions;

        //Panels which act as container controls and whose visibility needs to be set dynamically
        protected Panel panOutputSettings;
        protected Panel panImportSettings;
        protected Panel panImagePicker;
        protected Panel panLinkOptions;

        //The 3 treeview controls used for selecting component documents and images
        protected SPTreeView tvLibraryBrowser;
        protected SPTreeView tvSelected;
        protected SPTreeView tvImagePicker;

        //Buttons (of various shades) which facilitate the movement of document nodes
        //between and within treeviews
        protected Button btnLeft;
        protected Button btnRight;
        protected ImageButton ibtnMoveUp;
        protected ImageButton ibtnMoveDown;
        protected LinkButton lbtnMoveUp;
        protected LinkButton lbtnMoveDown;

        //Buttons used in the Image Picker treeview to expand or collapse all nodes
        //and to select and save PowerPoint slide selection
        protected ImageButton ibtnExpandAll;
        protected ImageButton ibtnCollapseAll;
        protected LinkButton lbtnSaveSlideSelection;
        protected ImageButton ibtnSaveSlideSelection;
        protected System.Web.UI.WebControls.CheckBox cbSelectAll;

        //Controls that set the view options in the library browser treeview
        protected DropDownList ddlViewPicker;
        protected ImageButton ibtnRefresh;
        protected System.Web.UI.WebControls.CheckBox cbExludeAdded;

        //Buttons that are used to save the current profile for the output document
        protected LinkButton lbtnSave;
        protected ImageButton ibtnSave;
    
        //Stores the output file name
        protected TextBox tbOutputFile;

        //Drop downs and radio button lists which allow the user to configure import amd export options
        protected DropDownList ddlInsertOption;
        protected DropDownList ddlOutputFormat;
        protected RadioButtonList rblSlideOptions;
        protected DropDownList ddlMetaDataSource;
        protected RadioButtonList rblOnBuild;
        protected RadioButtonList rblLinkOptions;
        protected TextBox tbCustomLinkText;

        //Build and Cancel buttons, top and bottom
        protected Button btnBuildTop;
        protected Button btnCancelTop;
        protected Button btnBuildBottom;
        protected Button btnCancelBottom;


    #endregion

        #region PlaceHolderLeftNavBarTop
        //Controls in the LeftNavBar area which show a document icon or preview image as well has 
        //display properties decribing the document in focus in one of the primary treeviews
        protected System.Web.UI.WebControls.Image imgPopupPreview;
        protected HyperLink lnkDocThumbnail;
        protected Label labDocInfo;

        #endregion


    #endregion

    #region Overidden Methods
    
    //Initialises and configures class variables and controls
    protected override void CreateChildControls()
    {
        base.CreateChildControls();

        //Read the web url parameters passed in via the ECB menu item
        ListID = Page.Request.Params["ListID"];
        ItemID = Page.Request.Params["ItemID"];
        ItemUrl = Page.Request.Params["ItemUrl"];
        WebUrl = Page.Request.Params["WebUrl"];

        //From these we can instantiate class variables for the list and document objects 
        List = this.Web.Lists[new Guid(ListID)];
        MasterDocItem = List.GetItemById(Convert.ToInt32(ItemID));

        //Get a reference to the document file and its Url
        File = MasterDocItem.File;
        FileUrl = File.Url;

        //Quit here if this is as postback rather than an initial page load
        if (Page.IsPostBack) return;

        //Because Page.Request.UrlReferrer is null we have to chop up
        //the ItemUrl parameter to see where we've come from and so where
        //to send the user browser back too when done.
        ReferrerUrl = ItemUrl.Substring(0, ItemUrl.LastIndexOf("/"));

        //Set the urls of the breadcrumb controls to take the user back to either
        //the Library or the Web.
        //TODO: Should really deal with Navigation to folders as well
        lnkLib.NavigateUrl = ReferrerUrl;
        lnkLib.Text = List.Title;
        lnkWeb.NavigateUrl = Web.Url;
        lnkWeb.Text = Web.Title;

        //Load the ddlViewPicker drop down list control with available views defined in the library
        //The View becomes the Text for each list item and the unique ID (GUID) of the view is stored
        //as a string in the list item Value property.
        foreach (SPView View in List.Views)
            if ((!View.Hidden) && (View.Title != string.Empty))
                ddlViewPicker.Items.Add(new System.Web.UI.WebControls.ListItem(View.Title, View.ID.ToString()));

        //Once loaded set the initial view to be the default view and load it into ViewState
        ddlViewPicker.SelectedValue = List.DefaultView.ID.ToString();
        CurrentView = List.DefaultView;
        if (ViewState["CurrentView"] == null)
            ViewState.Add("CurrentView", ddlViewPicker.SelectedValue);
        else
            ViewState["CurrentView"] = ddlViewPicker.SelectedValue;

        //Set the page title lable to be the name of the master document file
        labPageTitleInTitleArea.Text = File.Name;

        //Load the tvSelected treeview with settings serialised out the "DocComponents" hidden field
        //in the master document
        InitialiseSelectedTreeview(tvSelected.Nodes, File);

        //Load the tvLibraryBrowser with nodes that represent items returned from the view
        InitialiseLibraryBrowserTreeview(tvLibraryBrowser.Nodes, CurrentView);

        //Set the default output file name in the tbOutputFile text box control
        tbOutputFile.Text = GetDefaultOutputFile(MasterDocItem);

        //Set the default on build redirect option, read from the OnBuildRedirect field in the master document item
        rblOnBuild.SelectedIndex = GetDefaultRedirectOption(MasterDocItem);

        //Set the default text for the tbCustomLinkText text box control
        tbCustomLinkText.Text = cDefaultLinkText;
    }

    //Attach the event handlers to the controls
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        //Clones all the checked nodes in the tvLibraryBrowser treeview and 
        //add the clones as new nodes in the tvSelected treeview
        btnRight.Click += new EventHandler(btnRight_Click);

        //Removes the selected node (if the node represents a component file)
        //from the tvSelected treeview
        btnLeft.Click += new EventHandler(btnLeft_Click);

        //Moves the selected node (again, if the node represents a component file
        //rather than a content control section marker in the master document) up
        //in the order of the tvSelected treeview nodes collection 
        lbtnMoveUp.Click += new EventHandler(MoveUp_Click);
        ibtnMoveUp.Click += new ImageClickEventHandler(ibtnMoveUp_Click);

        //As above but moves the selected node down the order (rather than up)
        lbtnMoveDown.Click += new EventHandler(MoveDown_Click);
        ibtnMoveDown.Click += new ImageClickEventHandler(ibtnMoveDown_Click);

        //Saves the configuration in fields of the master document template
        lbtnSave.Click += new EventHandler(lbtnSave_Click);

        //Reload the tvLibraryBrowser treeview when the user selects a differnt view in the
        //ddlViewPicker drop down list control
        ddlViewPicker.SelectedIndexChanged += new EventHandler(ddlViewPicker_SelectedIndexChanged);

        //Allows the user to force a refresh (reload) of the tvLibraryBrowser treeview 
        ibtnRefresh.Click += new ImageClickEventHandler(ddlViewPicker_SelectedIndexChanged);

        //Configures the UI according to the node that is selected in the tvSelected treeview
        tvSelected.SelectedNodeChanged += new EventHandler(tvSelected_SelectedNodeChanged);

        //Configures the UI according to the node that is selected in the tvLibraryBrowser treeview       
        tvLibraryBrowser.SelectedNodeChanged += new EventHandler(tvLibraryBrowser_SelectedNodeChanged);

        //The following 3 events trigger a call the same the handler code to configure the UI according
        //to which control options have been set by the user
        ddlInsertOption.SelectedIndexChanged += new EventHandler(ConfigureControls);
        rblSlideOptions.SelectedIndexChanged += new EventHandler(ConfigureControls);
        tbCustomLinkText.TextChanged += new EventHandler(ConfigureControls);

        //This event handler also calls ConfigureControls but first enables or disables the
        //the tbCustomLinkText textbox control according to the option selected by the user
        //in the rblLinkOptions radio button list control
        rblLinkOptions.SelectedIndexChanged += new EventHandler(rblLinkOptions_SelectedIndexChanged);

        //Handlers to expand/collapse/select/deselect items in the tvImagePicker treeview
        ibtnExpandAll.Click += new ImageClickEventHandler(ibtnExpandAll_Click);
        ibtnCollapseAll.Click += new ImageClickEventHandler(ibtnCollapseAll_Click);
        cbSelectAll.CheckedChanged += new EventHandler(cbSelectAll_CheckedChanged);

        //Saves the list of selected slides to view state
        lbtnSaveSlideSelection.Click += new EventHandler(lbtnSaveSlideSelection_Click);

        //Builds the compound document according to the component files and their settings as
        //specified by the user
        btnBuildTop.Click += new EventHandler(btnBuild_Click);
        btnBuildBottom.Click += new EventHandler(btnBuild_Click);

        //Cancels out of the build process and returns the user to the document library
        btnCancelTop.Click += new EventHandler(btnCancel_Click);
        btnCancelBottom.Click += new EventHandler(btnCancel_Click);
    }


    //Stops us dead in our tracks if the selected master template item has no defined
    //insertion points, otherwise selects the first nodes in the treeviews
    protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
        if (Page.IsPostBack) return;

        //If there are no import points defined in the source document then there is nothing to
        //do but display a warning message to the user
        if (tvSelected.Nodes.Count == 0)
        {
            btnRight.Enabled = false;
            btnLeft.Enabled = false;
            RaiseWarning(cWarningNoSectionsFound);
        }
        else
        {
            TreeNode FirstNode = tvSelected.Nodes[0];
            FirstNode.Selected = true;
            tvSelected_SelectedNodeChanged(tvSelected, new EventArgs());
        }
    }

    #endregion

    #region Event Handlers
    
    //Redirect back to the library when the user clicks either Cancel button
    void btnCancel_Click(object sender, EventArgs e)
    {
        Page.Response.Redirect(lnkLib.NavigateUrl);
    }

    //Build the compound document
    void btnBuild_Click(object sender, EventArgs e)
    {

        //Save the current profile back to the master document item
        lbtnSave_Click(sender, e);

        //Prevent saving the output file back over the source
        if (System.IO.Path.GetFileNameWithoutExtension(File.Name).ToUpper() == tbOutputFile.Text.ToUpper())
        {
            RaiseWarning("You can't save the output to the source file.");
            return;
        }

        //Create the output file by first getting a path to the new file name which will be 
        //in the same location (Library/Folder) as the master document but with a file name provided
        //by the user (the default value having been retrieved from property of the master document)
        //The file extension is provided by the selecetd value ddlOutputFormat drop down list control.
        //Note that currently only .docx is supported but I reckon that the new Office services in
        //SharePoint 2010 will allow me to save the output to other formats such as pdf and xps directly.

        string NewFilePath = File.ParentFolder.Url + "/" + tbOutputFile.Text + ddlOutputFormat.SelectedValue;
        try
        {
            //Get the value of the OverwriteOutputFile on the master document to set the Overwrite 
            //parameter of the CopyTo call.  If Overwrite is false and a file of the specified name already
            //exists then this will trigger an exception which we handle by reporting a warning via a call to
            //RaiseWarning which simply sets the labWarning control to display the exception message.
            Boolean Overwrite = Convert.ToBoolean(MasterDocItem["OverwriteOutputFile"].ToString());

            RemoveThumbnails(NewFilePath, Overwrite);

            //CopyTo actaully creates a new document item based on the master document
            File.CopyTo(NewFilePath, Overwrite);


            //Next we get a reference to the new file that was generated when the master document was copied
            SPFile NewFile = File.ParentFolder.Files[NewFilePath];

            //Create a stream so the new file we can manipulate its contents via the Open XML SDK
            Stream FileStream = NewFile.OpenBinaryStream(SPOpenBinaryOptions.SkipVirusScan);

            //Here's where we open the new document stream in the OpenXML SDK  
            using (WordprocessingDocument NewWordMLDoc = WordprocessingDocument.Open(FileStream, true))
            {
                //Get a reference to the main part of the document because that's what we are going to edit
                MainDocumentPart MainDocPart = NewWordMLDoc.MainDocumentPart;

                //The RecurseForContentControls is a helper method which takes in an IEnumerable collection
                //of OpenXML elements i.e. IEnumerable<OpenXmlElement> and returns a list of SdtElement objects.
                //SdtElement is the base class for content controls.  In this case it returns all content controls
                //in the main body of the document.  Note we need to use a list of SdtElement because not all
                //content controls are of the same class, for example controls in simple paragraphs end up as being
                //StdBlock whereas content controls in tables end up as SdtCell
                List<SdtElement> ContentControls = RecurseForContentControls(MainDocPart.Document.Body.Elements());

                //Call to code which builds a section of the document, in this case I use the term section to mean
                //a collection of content control references (SdtElement).  All the heavy lifting is done in the
                //BuildSection method.
                BuildSection(File, MainDocPart, ContentControls);

                //We also need to deal with other sections of the document, namely headers and footers 
                //To do that we loop round every HeaderPart of the document, retrieve the content controls
                //and build the section accordingly
                foreach (HeaderPart HeaderPart in NewWordMLDoc.MainDocumentPart.HeaderParts)
                {
                    List<SdtElement> HeaderContentControls = RecurseForContentControls(HeaderPart.Header.Elements());
                    BuildSection(File, HeaderPart, HeaderContentControls);
                }

                //Do the same for the FooterParts as well
                foreach (FooterPart FooterPart in NewWordMLDoc.MainDocumentPart.FooterParts)
                {
                    List<SdtElement> FooterContentControls = RecurseForContentControls(FooterPart.Footer.Elements());
                    BuildSection(File, FooterPart, FooterContentControls);
                }
            }

            //Save the updated binary back to the file and then close the stream
            NewFile.SaveBinary(FileStream, false);
            FileStream.Close();

            //Next we need to update the meta-data of the newly created document item.  The application provides a
            //a drop down list control which allows users to select one of the component documents to act as the
            //master source for the final content type and default meta-data values of the new document.
            //The selected MetaDataSourceItem is persisted through a hidden text field on the master content type
            //where the reference to the user's selected document is stored as a Guid value in string format

            //Set up some reference objects
            SPListItem MetaDataSourceItem = null;
            SPListItem NewItem = NewFile.Item;
            SPList ParentList = NewItem.ParentList;
            Guid MetaDataSourceId = Guid.Empty;

            //If no meta-data source is specified then just set the content type to
            //be the default content type for the list
            if (ddlMetaDataSource.SelectedValue == string.Empty)
            {
                SPContentType DefCT = ParentList.RootFolder.ContentTypeOrder[0];
                if (ParentList.IsContentTypeAllowed(DefCT))
                {
                    NewItem["ContentTypeId"] = DefCT.Id;
                    NewItem.SystemUpdate(false);
                }

            }
            //Otherwise set the content type to be the same as that of the metadata source
            //and then map accross the editabel field properties 
            else
            {
                MetaDataSourceId = new Guid(ddlMetaDataSource.SelectedValue);
                if (MetaDataSourceId != Guid.Empty)
                    MetaDataSourceItem = NewItem.ParentList.GetItemByUniqueId(MetaDataSourceId);
                {
                    if (MetaDataSourceItem != null)
                    {
                        //First reset the content type to be the same as the source
                        NewItem["ContentTypeId"] = MetaDataSourceItem.ContentType.Id;
                        NewItem.SystemUpdate(false);

                        //Then we cam transfer the meta-data property values from source to target
                        //We use a Dirt boolean variable just so we can make the call to SystemUpdate
                        //only when really needed.
                        Boolean Dirty = false;
                        foreach (SPField Field in MetaDataSourceItem.Fields)
                            try
                            {
                                //We're only interested in editable fields here and not the Name field of
                                //course because that would be changing the file name and therefore url 
                                if ((Field.ReadOnlyField == false) &&
                                   (Field.Title != "Name"))
                                {
                                    NewItem[Field.Id] = MetaDataSourceItem[Field.Id];
                                    //Ok we've changed a value so now we need to call SystemUpdate so we
                                    //flag this by setting Dirty to true
                                    Dirty = true;
                                }
                            }
                            catch (Exception ex)
                            {
                                RaiseWarning(ex.Message);
                            }
                        //Only call SystemUpdate when really needed
                        if (Dirty)
                            NewItem.SystemUpdate(false);
                    }
                }

            }

            //Almost done, just need to redirect the browser somewhere useful such as edit page of the
            //new document, the document itself orback to the document library.  The location is set by
            //the user in the rblOnBuild radio button list control.
            string RedirectUrl = string.Empty;
            if (rblOnBuild.SelectedItem.Text == "Stay Here")
                return;
            else if (rblOnBuild.SelectedItem.Text == "Review Properties")
                RedirectUrl = ParentList.ParentWeb.Url + "/" + File.Item.ParentList.Title +
                "/Forms/EditForm.aspx?ID=" + NewItem.ID.ToString();
            else if (rblOnBuild.SelectedItem.Text == "Open Document")
                RedirectUrl = ParentList.ParentWeb.Url + "/" + NewFile.Url;
            else if (rblOnBuild.SelectedItem.Text == "Go to Library")
                RedirectUrl = lnkLib.NavigateUrl;

            Page.Response.Redirect(RedirectUrl);
        }
        catch (Exception ex)
        {
            RaiseWarning(ex.Message);
        }
    }

    //Checks to see if there are existing thumbnail images for the document and deletes them if there are
    private void RemoveThumbnails(string NewFilePath, bool Overwrite)
    {
        SPFile DocFile;
        SPListItem DocItem;
        SPList Library;
        SPFolder RootFolder;


        if (Web.GetFile(NewFilePath).Exists)
            DocFile = Web.GetFile(NewFilePath);
        else
            return;

        DocItem = DocFile.Item;
        Library = DocItem.ParentList;
        RootFolder = Library.RootFolder;
        

        //SPListItem Item = properties.ListItem;
        //SPFile DocFile = Item.File;
        //SPList List = Item.ParentList;
        //SPFolder RootFolder = List.RootFolder;
        //SPWeb Web = List.ParentWeb;
        string ThumbnailPath;

        //Delete the file from _t if it exists
        ThumbnailPath = Web.Url + "/" + RootFolder.Url + "/_t/" + DocItem.ID.ToString() + ".png";
        if (Web.GetFile(ThumbnailPath).Exists)
            Web.GetFile(ThumbnailPath).Delete();

        ////Similiary delete the file from _w
        ThumbnailPath = Web.Url + "/" + RootFolder.Url + "/_w/" + DocItem.ID.ToString() + ".png";
        if (Web.GetFile(ThumbnailPath).Exists)
            Web.GetFile(ThumbnailPath).Delete();
    }

    //Pumps out to View State the selected image nodes in the image picker
    void lbtnSaveSlideSelection_Click(object sender, EventArgs e)
    {
        TreeNode SelectedNode = GetSelectedNodeFromViewState();
        int ItemID = NodeValueToItemId(SelectedNode.Value);
        SPListItem Item = List.GetItemById(ItemID);
        string Key = Item.UniqueId.ToString();
        string Value = string.Empty;
        int i = 1;

        //Loop round the nodes to build up a comma separated list of indices of selected items
        foreach (TreeNode Node in tvImagePicker.Nodes)
        {
            if (Node.Checked)
                Value = Value + i.ToString() + ",";
            i++;
        }
        Value = Value.TrimEnd(',');

        if ((ViewState[Key] == null) && (Value != string.Empty))
            ViewState.Add(Key, Value);
        else
            if (Value == string.Empty)
                ViewState.Remove(Key);
            else
                ViewState[Key] = Value;
    }

    //Checks or Unchecks all the image nodes in the image picker
    void cbSelectAll_CheckedChanged(object sender, EventArgs e)
    {
        foreach (TreeNode Node in tvImagePicker.Nodes)
            Node.Checked = cbSelectAll.Checked;
    }

    //Collapses all nodes in the image picker
    void ibtnCollapseAll_Click(object sender, ImageClickEventArgs e)
    {
        tvImagePicker.CollapseAll();
    }

    //Expands all nodes in the image picker
    void ibtnExpandAll_Click(object sender, ImageClickEventArgs e)
    {
        tvImagePicker.ExpandAll();
    }

    //Enable the tbCustomLinkText textbox only when the selected value in the rblLinkOptions
    //is 3 i.e. 'Custom link text' and set the default text value as required.  Then call the
    //ConfigureControls method which stores the user selected properties in the Value property
    //of the treenode and configures the UI according to the property values selected by the user.
    void rblLinkOptions_SelectedIndexChanged(object sender, EventArgs e)
    {
        if (rblLinkOptions.SelectedValue == "3")
        {
            if (tbCustomLinkText.Text == cDefaultLinkText)
                tbCustomLinkText.Text = string.Empty;
            tbCustomLinkText.Enabled = true;
        }
        else
        {
            if (tbCustomLinkText.Text == string.Empty)
                tbCustomLinkText.Text = cDefaultLinkText;
            tbCustomLinkText.Enabled = false;
        }
        ConfigureControls(sender, e);
    }

    //Seralises the settings in the tvSelected treeview and saves it as an XML fragment in the
    //"DocComponents" hidden field of the master document template.  It also saves the meta-data
    //source object (Guid UniqueID as string) to the "MetaDataSource" hidden field.
    void lbtnSave_Click(object sender, EventArgs e)
    {
        ClearWarning();
        string FieldValue = SerializeTreeView(tvSelected);
        MasterDocItem["DocComponents"] = FieldValue;
        MasterDocItem["MetaDataSource"] = ddlMetaDataSource.SelectedValue;
        MasterDocItem.Update();  //Not sure whether Update or SystemUpdate would be best here
    }

    //Simply calls the MoveDown_Click handler
    void ibtnMoveDown_Click(object sender, ImageClickEventArgs e)
    {
        MoveDown_Click(sender, e);
    }

    //Simply calls the MoveUp_Click handler
    void ibtnMoveUp_Click(object sender, ImageClickEventArgs e)
    {
        MoveUp_Click(sender, e);
    }

    //Moves the selected node  in the tvSelected up the tree
    void MoveUp_Click(object sender, EventArgs e)
    {
        ClearWarning();

        //Get the currently selected node from the view state and set up references
        //to its parent node and its index in the child node collection of the parent
        TreeNode SelectedNode = GetSelectedNodeFromViewState();
        TreeNode ParentNode = SelectedNode.Parent;
        int CurrentNodeIndex = ParentNode.ChildNodes.IndexOf(SelectedNode);

        //Clone the node 
        TreeNode Clone = CloneNode(SelectedNode);

        //Check to see if this is the selected node is the first item in the collection
        //in which case tack the clone onto the previous sibling node of the parent
        //otherwise add the clone above the previous sibling of the selected node
        if (CurrentNodeIndex == 0)
        {
            int IndexOfParentNode = tvSelected.Nodes.IndexOf(ParentNode);
            TreeNode ParentSiblingPrevious = tvSelected.Nodes[IndexOfParentNode - 1];
            ParentSiblingPrevious.ChildNodes.Add(Clone);
        }
        else
            ParentNode.ChildNodes.AddAt(CurrentNodeIndex - 1, Clone);

        //Finally remove the old node and select the clone and call the 
        //SelectedNodeChanged event handler to save the view state and 
        //set the selected section indicator icon
        ParentNode.ChildNodes.Remove(SelectedNode);
        Clone.Select();
        tvSelected_SelectedNodeChanged(sender, e);
    }

    //Moves the selected node  in the tvSelected up the tree
    void MoveDown_Click(object sender, EventArgs e)
    {
        ClearWarning();

        //Get the currently selected node from the view state and set up references
        //to its parent node and its index in the child node collection of the parent
        TreeNode SelectedNode = GetSelectedNodeFromViewState();
        TreeNode ParentNode = SelectedNode.Parent;
        int CurrentNodeIndex = ParentNode.ChildNodes.IndexOf(SelectedNode);

        //Clone the node 
        TreeNode Clone = CloneNode(SelectedNode);

        //Check to see if this is the selected node is the last item in the collection
        //in which case tack the clone onto the next sibling node of the parent
        //otherwise add the clone below the previous sibling of the selected node
        if (CurrentNodeIndex == ParentNode.ChildNodes.Count - 1)
        {
            int IndexOfParentNode = tvSelected.Nodes.IndexOf(ParentNode);
            TreeNode ParentSiblingNext = tvSelected.Nodes[IndexOfParentNode + 1];
            ParentSiblingNext.ChildNodes.AddAt(0, Clone);
        }
        else
            ParentNode.ChildNodes.AddAt(CurrentNodeIndex + 2, Clone);

        //Finally remove the old node and select the clone and call the 
        //SelectedNodeChanged event handler to save the view state and 
        //set the selected section indicator icon
        ParentNode.ChildNodes.Remove(SelectedNode);
        Clone.Select();
        tvSelected_SelectedNodeChanged(sender, e);
    }

    //Reloads the tvLibraryBrowser with the selected view.  The UniqueIdof the view is stored in the
    //Value property of list items in the ddlViewPicker (Guid as string).  The selected view is stored
    //in ViewState so it can be retrieved at any stage in the page lifecycle
    void ddlViewPicker_SelectedIndexChanged(object sender, EventArgs e)
    {
        ClearWarning();
        CurrentView = List.Views[new Guid(ddlViewPicker.SelectedValue)];
        if (ViewState["CurrentView"] == null)
            ViewState.Add("CurrentView", ddlViewPicker.SelectedValue);
        else
            ViewState["CurrentView"] = ddlViewPicker.SelectedValue;
        labDocInfo.Text = string.Empty;
        InitialiseLibraryBrowserTreeview(tvLibraryBrowser.Nodes, CurrentView);
    }

    //The tvLibraryBrowser treeview control contains nodes which represent documents in the
    //library.  The document nodes actually created depend on settings in other controls.  Specifically
    //the ddlViewPicker control allows the user to specifiy the view and the cbExcludeAdded checkbox is
    //used (when checked) to filter out items which have already been added to the tvSelected treeview

    //When the user selects a node in the tvLibraryBrowser treeview then this handler calls the 
    //ConfigureItemDetails method with a reference to the item in the document library that the
    //node represents which configures the controls in the Left Nav Bar areas to display some useful 
    //information describing the document.
    void tvLibraryBrowser_SelectedNodeChanged(object sender, EventArgs e)
    {
        ClearWarning();  //Clear any warining messages from the UI

        int SelectedId = Convert.ToInt32(NodeValueToItemId(tvLibraryBrowser.SelectedValue));
        SPListItem SelectedItem = List.Items.GetItemById(SelectedId);
        ConfigureItemDetails(SelectedItem);

        //This (rather crude) method strips off all markup tags that indicate that any node was selected
        StripSelectedTags(tvLibraryBrowser.Nodes);

        foreach (TreeNode Node in tvSelected.Nodes)
            StripSelectedTags(Node.ChildNodes);

        //This method simply adds markup tags which indicate that the current node is selected
        tvLibraryBrowser.SelectedNode.Text = AddSelectedTags(tvLibraryBrowser.SelectedNode.Text);
    }

    //When a user selects a node in the tvSelected treeview controls
    void tvSelected_SelectedNodeChanged(object sender, EventArgs e)
    {
        //Clear an UI warning messages 
        ClearWarning();

        //Get a reference to newly selected treenode
        TreeNode SelectedNode = tvSelected.SelectedNode;

        //This treenode will by used to identify the root node which in
        //effect will represent the document insertion point content control.
        TreeNode SelectedSectionNode = null;

        //The cSelectedSectionUrl constant points to a graphic that indicates the
        //active the insertion point.  As this may have changed we need to clear all 
        //the image urls from the section nodes.  Then we call the StripSelectedTags
        //method for all child nodes of each root node to clear the markup tags
        //which indicate selected nodes.
        foreach (TreeNode Node in tvSelected.Nodes)
        {
            if (Node.ImageUrl == cSelectedSectionUrl)
                Node.ImageUrl = string.Empty;
            StripSelectedTags(Node.ChildNodes);
        }

        //If selected node is at Level 0 then it is a section/root node representing an insertion point
        //so we just configure the UI to hide controls that don't make sense in this context and set the
        //SelectedSectionNode object to the selected node.
        if (SelectedNode.Depth == 0)
        {
            panImportSettings.Visible = false;
            rblSlideOptions.Visible = false;
            labOptions.Visible = false;
            panImagePicker.Visible = false;
            SelectedSectionNode = SelectedNode;

            //Calling this with a null value will hide the controls which display some document details
            //which is what we want because this is a section node and does not represent a document
            ConfigureItemDetails(null);
        }

        //If however, the user selects a node which represents a selected component document we set the
        //SelectedSectionNode object to the parent (root) node of the selected node, add the markup tags
        //which indicate the currently selected node and then display the import settings panel (because
        //we now have a file node that might need configuring) and set the properties of edit controls
        //with the panel appropriately according to properties of the selected treenode and the file it
        //represents
        else
        {
            SelectedSectionNode = SelectedNode.Parent;
            SelectedNode.Text = AddSelectedTags(SelectedNode.Text);
            panImportSettings.Visible = true;
            ConfigureImportProfilePanel(SelectedNode);

            //Configure the controls in the Left Nav Panel to show some context information about the
            //document in focus
            int ItemID = NodeValueToItemId(SelectedNode.Value);
            ConfigureItemDetails(List.GetItemById(ItemID));
        }

        //The tvSelected treeview is in focus so strip out any selected node tags from the 
        //tvLibrary treeview
        StripSelectedTags(tvLibraryBrowser.Nodes);

        //This method just enables or disables the Move Up and Move Down buttons according to where the
        //selected node sits in the tree
        SetMoveButtons(SelectedNode);

        //Similarly this method enables/disables the Left button
        SetLeftButton(SelectedNode);

        //Set the image of the node which represents the section/document insertion point to show which
        //section is in focus and expand the section node to show its child nodes
        SelectedSectionNode.ImageUrl = cSelectedSectionUrl;
        SelectedSectionNode.Expand();


        //Now pump out references to the selected node and selected section node to ViewState.
        //The ViewState keys hold the node ValuePath as their properties which we insure is unqiue.
        //This way we can retrive references to these node at any point in the page lifecycle
        if (ViewState["SelectedSectionNodeValuePath"] == null)
            ViewState.Add("SelectedSectionNodeValuePath", SelectedSectionNode.ValuePath);
        else
            ViewState["SelectedSectionNodeValuePath"] = SelectedSectionNode.ValuePath;

        if (ViewState["SelectedNodeValuePath"] == null)
            ViewState.Add("SelectedNodeValuePath", SelectedNode.ValuePath);
        else
            ViewState["SelectedNodeValuePath"] = SelectedNode.ValuePath;

        //Finally we post fix the section nodes with text to show the number of component files
        //i.e.child nodes they contain.
        SetNodeComponentCount(tvSelected.Nodes);
    }

    //Copies all checked document nodes in tvLibraryBrowser and inserts them at the selected insertion point in the
    //tvSelecte treeview
    void btnRight_Click(object sender, EventArgs e)
    {
        ClearWarning();  //Clear any UI warning messages

        if ((tvLibraryBrowser.CheckedNodes.Count == 0) || (tvSelected.Nodes.Count == 0))
            return;

        TreeNode SelectedSectionNode = GetSelectedSectionNodeFromViewState();

        if (SelectedSectionNode == null)
            SelectedSectionNode = tvSelected.Nodes[0];

        TreeNode SelectedNode = GetSelectedNodeFromViewState();

        List<TreeNode> NodesToRemove = new List<TreeNode>();

        TreeNode NewNode;
        string SelectedComponents = ViewState["SelectedComponents"].ToString();

        foreach (TreeNode CheckedNode in tvLibraryBrowser.CheckedNodes)
        {
            NewNode = CopyNode(CheckedNode);

            //If the currently selected node in tvSelected is a section node then add it 
            //at the end of the nodes Collection else add it underneath the selected item
            if ((SelectedNode == null) || (SelectedNode.Depth == 0))
                SelectedSectionNode.ChildNodes.Add(NewNode);
            else
            {
                int SelectedNodeIndex = SelectedSectionNode.ChildNodes.IndexOf(SelectedNode);
                SelectedSectionNode.ChildNodes.AddAt(SelectedNodeIndex + 1, NewNode);
            }

            SPListItem Item = List.GetItemById(NodeValueToItemId(NewNode.Value));
            AddMetaDataSource(ddlMetaDataSource, Item);
            SelectedComponents += "~" + CheckedNode.Value + ";";
            NodesToRemove.Add(CheckedNode);
        }

        ViewState["SelectedComponents"] = SelectedComponents;

        if (cbExludeAdded.Checked)
            foreach (TreeNode NodeToRemove in NodesToRemove)
                tvLibraryBrowser.Nodes.Remove(NodeToRemove);
        SetNodeComponentCount(tvSelected.Nodes);
    }

    //Removes the selected document node from the tvSelected treeview and forces a refresh of the tvLibraryBrowser 
    //treeview which has the effect of moving the treenode from Right (tvSelected) to Left (tvLibraryBrowser)
    void btnLeft_Click(object sender, EventArgs e)
    {
        ClearWarning();  //Clear any UI warning messages that might be displayed

        //Retrieve the selected node in the tvSelected treeview from ViewState
        TreeNode SelectedNode = GetSelectedNodeFromViewState();

        //We're only interested in document nodes i.e. nodes at depth 1 because we can't shift the document
        //insertion section point nodes, that make no sense. 
        if (SelectedNode.Depth != 0)
        {
            //Now we know we have a document node so we need to get its parent
            string SelectedNodeValue = SelectedNode.Value;
            TreeNode SelectedSectionNode = SelectedNode.Parent;

            //Get the ID of document that the node represents
            int ItemID = NodeValueToItemId(SelectedNodeValue);

            //Remove the node from the ChildNodes collection of the parent section node
            SelectedSectionNode.ChildNodes.Remove(SelectedNode);

            //Get the ViewState string which contains the '~' ';' delimited Ids of the list items in the
            //tvSelected treeview and remove from that string the reference to the document node we have
            //just removed, then save the updated string back to ViewState
            string SelectedComponents = ViewState["SelectedComponents"].ToString();
            if (SelectedComponents.Contains(SelectedNodeValue))
                SelectedComponents = SelectedComponents.Replace(SelectedNodeValue, string.Empty);
            ViewState["SelectedComponents"] = SelectedComponents;

            //Forces a reloading of the tvLibraryBrowser treeview 
            ddlViewPicker_SelectedIndexChanged(sender, e);

            //Loop round the tvLibraryBrowser treeview and select the node which represents the item we just removed 
            //from the tvSelected treeview.  This give the effect that focus remains with the document node (even 
            //though they are in fact completly different treenodes who just represent the same document).
            foreach (TreeNode Node in tvLibraryBrowser.Nodes)
                if (NodeValueToItemId(Node.Value) == ItemID)
                {
                    Node.Select();
                    Node.Text = AddSelectedTags(Node.Text);
                    break;
                }
            //Remove the item from the list of possible metadata/Content Type sources
            RemoveMetaDataSource(ref ddlMetaDataSource, List.GetItemById(ItemID));
        }

        //Set the enabled status of the btnLeft button.  This will normally disable the button because either the
        //selected node was a section/insertion point node or the act of clicking Left will have removed the selected
        //node from tvSelected so that treeview will have no selected node i.e. will = null.
        SetLeftButton(tvSelected.SelectedNode);
    }


    #endregion

    #region Misc Support and Helper Methods

    //This code is triggered from several places and as event handlers.
    //This is where the options that the user has set in a number of UI controls
    //are stored as properties in the Value string property of the associated treenode
    //and then these properties are used to configure the UI with the call to SetControls 
    void ConfigureControls(object sender, EventArgs e)
    {
        //Get the currently selected node in the tvSelected treeview (stored in ViewState
        //and retrieved FindNode using the ValuePath property of the node)
        TreeNode SelectedNode = GetSelectedNodeFromViewState();
        try
        {
            //Get the user's selected options for the 4 key properties from UI controls and 
            //and convert them to the corresponding enum and string values.
            ImportOptions ImportOpt = (ImportOptions)Convert.ToInt32(ddlInsertOption.SelectedValue);
            ImageOptions ImageOpt = (ImageOptions)Convert.ToInt32(rblSlideOptions.SelectedValue);
            LinkOptions LinkOpt = (LinkOptions)Convert.ToInt32(rblLinkOptions.SelectedValue);
            string CustomLinkText;
            if (tbCustomLinkText.Text == string.Empty)
                CustomLinkText = cDefaultLinkText;
            else
                CustomLinkText = tbCustomLinkText.Text;

            //Set the node value to be in the form 
            //ItemID#UniqueNideID#ImportOption|ImageOption|LinkOption|CustomLinkText
            SelectedNode.Value = SetNodeValue(SelectedNode.Value, ImportOpt, ImageOpt,
                                                                    LinkOpt, CustomLinkText);

            //Because we have changed the value path of the selected node we need to poke 
            //that back into the view state
            if (ViewState["SelectedNodeValuePath"] == null)
                ViewState.Add("SelectedNodeValuePath", SelectedNode.ValuePath);
            else
                ViewState["SelectedNodeValuePath"] = SelectedNode.ValuePath;

            //Get the library list item an call the SetControls method which actually configures
            //the UI according to the properties set by the user in the 
            int ItemID = NodeValueToItemId(SelectedNode.Value);
            SPListItem Item = List.GetItemById(ItemID);
            SetControls(ImportOpt, ImageOpt, HasImages(Item), Item);
        }

        catch (Exception ex)
        {
            RaiseWarning(ex.Message);
        }
    }

    //Sets the Value of the Node in the format
    //ListItemID#Guid#ImportOption|ImageOption|LinkOption|CustomLinkText
    //The ListItemID and the Guid remain unchanged in the returned string but the
    //properties are set according to the parameters passed in
    private string SetNodeValue(string CurrentVal, 
                                ImportOptions ImportOption,
                                ImageOptions ImageOption, 
                                LinkOptions LinkOption, 
                                string CustomLinkText)
    {
        string[] NodeValueComponents = CurrentVal.Split('#');
        return NodeValueComponents[0] + "#" + NodeValueComponents[1] + "#" +
            Convert.ToInt32(ImportOption).ToString() + "|" +
            Convert.ToInt32(ImageOption).ToString() + "|" +
            Convert.ToInt32(LinkOption).ToString() + "|" +
            CustomLinkText;
    }

    //Generated the default output file name based on the OutFileName field in the master document.
    //Note that the solution provides a custom content type (Fusion Template) complete with a few 
    //custom site column field.  The internal name of one of these fields is called OutFileName and
    //is a simple text field that is used as the filename for the resulting compound document.  This
    //field can be configured to contain a [SourceFilename] parameter and/or a date/time format string,
    //again within square brackets (I would have preferred to use the brace characters '{' and '}' but
    //these caused grief when used in the CAML Field definition).  This method parses the output file 
    //name string and replaces these parameters with actual values taken from the master template source
    //file and the current DateTime Now value respectively.

    //Finally, note that this is the default file name only.  The actual output file name might have been
    //overriden to something different by a user entered value in the tbOutputFile text box control.
    private string GetDefaultOutputFile(SPListItem DocItem)
    {
        //When all else fails at least return something as the file name
        string Result = "Output File Name";

        //Read the OutFileName property from the Content Type of the master template document.
        //Then parse the field value and replace parameter values if found.
        try
        {
            //Get the raw value from the "OutFileName" field
            string DefaultFilename = DocItem["OutFileName"].ToString();

            //Search for the "[SourceFilename]" token and if found replace it with file name 
            //(less extension) of the master document template
            if (DefaultFilename.Contains("[SourceFilename]"))
                DefaultFilename = DefaultFilename.Replace("[SourceFilename]",
                                   System.IO.Path.GetFileNameWithoutExtension(File.Name));

            //See if we have a date/time format string that needs processing 
            string DateFormatString = string.Empty;
            int DateFormatStart = DefaultFilename.IndexOf('[');
            int DateFormatEnd = DefaultFilename.IndexOf(']');
            if ((DateFormatEnd != -1) && (DateFormatStart != -1))
                DateFormatString = DefaultFilename.Substring(DateFormatStart + 1, DateFormatEnd - DateFormatStart - 1);
            if (DateFormatString != string.Empty)
            {
                DefaultFilename = DefaultFilename.Replace("[" + DateFormatString + "]",
                                                  DateTime.Now.ToString(DateFormatString));
                //For time formats we need to replace the invalid ':' characters with legal '.' characters
                //otherwise we'll get a file name error when trying to save the output file.
                DefaultFilename = DefaultFilename.Replace(':', '.');
            }
            Result = DefaultFilename;
        }
        catch (Exception ex)
        {
            //Raise a warning message if the data/time token is an invalide format for example
            RaiseWarning(ex.Message);
        }
        return Result;
    }

    //The DocFusion content type provides a field OnBuildRedirect in which the default
    //redirect option is stored.  This method returns an index value which keeps the 
    //selected item of the radio check box list (rblOnBuild) in synch
    private int GetDefaultRedirectOption(SPListItem DocItem)
    {
        try
        {
            string DefaultRedirect = DocItem["OnBuildRedirect"].ToString();
            if (DefaultRedirect == "Review Properties") return 0;
            else if (DefaultRedirect == "Open Document") return 1;
            else if (DefaultRedirect == "Go to Library") return 2;
            else if (DefaultRedirect == "Stay Here") return 3;
            else return 0;
        }
        catch (Exception ex)
        {
            RaiseWarning(ex.Message);
        }
        return 0;
    }

    //Fills the treeview with nodes that represent files in the selected view
    //of the document library
    private void InitialiseLibraryBrowserTreeview(TreeNodeCollection Nodes, SPView View)
    {
        TreeNode NewNode;

        //Get all the items in the view ready for loading into the treeview
        SPListItemCollection ViewItems = View.ParentList.GetItems(View);

        string SelectedComponents = ViewState["SelectedComponents"].ToString();

        Nodes.Clear();
        foreach (SPListItem Item in ViewItems)
        {
            //We need to decide whether the item can be added to the treeview and therefore needs a node
            Boolean CanAdd = true;
            if ((Item.Folder != null) || //Exclude folders
                (Item.UniqueId == MasterDocItem.UniqueId) || //Exclude the master document itself
                ((cbExludeAdded.Checked) && (!string.IsNullOrEmpty(SelectedComponents)) &&
                 (AlreadySelected(Item.ID)))) //If the ExcludeAdded check box is checked then only return
                CanAdd = false;               //true if the item is not already in the tvSelected treeview

            //If we are allowed to add a new node then do so by calling AddDocNode with the appropriate
            //default parameter values
            if (CanAdd)
            {
                ImportOptions DefaultImportOpt = DefaultImportOption(System.IO.Path.GetExtension(Item.File.Name));
                NewNode = AddDocNode(Nodes, List, Item.ID, DefaultImportOpt,
                                     ImageOptions.All, LinkOptions.Filename, cDefaultLinkText, false);
                NewNode.ShowCheckBox = true;
            }
        }
    }

    //Returns the default import format depending on file type extension.  Basically it returns 
    //ImportOption.Chunk for all the formats that can be handled that way and ImportOption.LargeImages
    //for graphic file types and in all other cases retunr ImportOption.Link because all component files
    //can at least be embedded as a hyperlink
    private ImportOptions DefaultImportOption(string Ext)
    {
        switch (Ext.ToLower())
        {
            case (".docx"):
            case (".docm"):
            case (".dotx"):
            case (".dotm"):
            case (".rtf"):
            case (".html"):
            case (".xhtml"):
            case (".mht"):
            case (".xml"):
                return ImportOptions.Chunk;
            case (".jpg"):
            case (".bmp"):
            case (".png"):
            case (".emf"):
            case (".wmf"):
            case (".gif"):
                return ImportOptions.LargeImage;
            default:
                return ImportOptions.Link;
        }
    }

    //This method is used to determine whether a treenode representing a document already exists in the 
    //tvSelected treeview.  It checks every child node of each  root node in the tvSelected treeview
    //and returns true when (and if) there is a node which matches in the DocID passed in
    private Boolean AlreadySelected(int DocID)
    {
        Boolean Result = false;
        foreach (TreeNode SectionNode in tvSelected.Nodes)
            if (SectionNode.ChildNodes.Count > 0)
                foreach (TreeNode ChildNode in SectionNode.ChildNodes)
                    if (NodeValueToItemId(ChildNode.Value) == DocID)
                        return true;
        return Result;
    }

    //Generates a tooltip string based on the SPFile and SPView objects passed in 
    protected string NodeTooltip(SPListItem DocItem, SPView View)
    {
        //Start with the file name
        string Result = "FileName: " + DocItem.Name + Environment.NewLine;
        SPField ViewField;

        //Then loop round all the fields (ViewFields) in the passed in view and add the
        //property name and value on a new line in the tooltip text string to be returned
        //Note that for a tooltip the Environment.NewLine string must be used to get a
        //line separation between Property/Value pairs.
        foreach (string ViewFieldName in View.ViewFields)
        {
            ViewField = DocItem.Fields.GetFieldByInternalName(ViewFieldName);
            Result += ViewField.Title + ": " +
                ViewField.GetFieldValueAsText(DocItem[ViewField.Id]) + Environment.NewLine;
        }
        return Result;
    }

    //In a similar fashion to the NodeTooltip method the DocInfo method returns a text string
    //which consists of the Property Value pairs for fields defined in a view.  In this case the
    //source View is taken by reading the unique id for the View which was poked into ViewState when
    //the user selects a view value in the ddlViewPicker control.  The resulting string is injected
    //into the labDocInfo control.  Unlike the tootip text which requires Environment.Newline a label
    //control will happliy accept an embedded "<br>" tag tosignal a line break.
    protected string DocInfo(SPListItem DocItem)
    {
        string Result = string.Empty;
        SPView PropertyView = List.DefaultView;
        SPFile File = DocItem.File;
        SPField ViewField;
        string FileName = File.Name;


        //Get the view to use by extracting its unquie id from ViewState if we can
        if (!string.IsNullOrEmpty(ViewState["CurrentView"].ToString()))
            try
            {
                string CurrentViewID = ViewState["CurrentView"].ToString();
                PropertyView = List.Views[new Guid(CurrentViewID)];
            }
            catch { }

        //Loop round all the ViewFields and assemble the Property/Value pair and add that
        //to the returned string, separated by "<b>" line break tags.  Note that the 
        //Property lables are in enboldened.
        foreach (string ViewFieldName in PropertyView.ViewFields)
        {
            ViewField = DocItem.Fields.GetFieldByInternalName(ViewFieldName);
            Result += "<b>" + ViewField.Title + ":</b> " +
                ViewField.GetFieldValueAsText(DocItem[ViewField.Id]) + "<br>";
        }
        return Result;
    }

    //Replaces content controls with actual content the file based on how the user has structured 
    //the treeview which maps out which content should be inserted where
    private void BuildSection(SPFile File, OpenXmlPart DocPart, List<SdtElement> ContentControls)
    {
        TreeNode SectionNode = null;

        //Loop round the list of content controls
        foreach (SdtElement ContentControl in ContentControls)
        {
            //Get the section node in the treeview to which the content control is associated.
            //This is setup when the treeview is created such that each root node represents an
            //insertion point i.e. content control in the document the name of which is stored in
            //the Value property of the node and so can be retrieved by a call to FindControl
            SectionNode = tvSelected.FindNode(ContentControl.InnerText);
            SPFile ComponentFile;

            //Now loop round all the child nodes of the section node and insert the component documents
            //Each component document is represented by one of these child nodes
            for (int i = SectionNode.ChildNodes.Count - 1; i > -1; i--)
            {
                TreeNode DocNode = SectionNode.ChildNodes[i];

                //NodeValueToItemId splits up the Value string of the child node and returns the Id of the
                //ListItem that the node represents.  The Node Value string is stored in the format
                //ID#UniqueNodeId#Properties where properties are separated with |.
                ComponentFile = List.GetItemById(NodeValueToItemId(DocNode.Value)).File;

                //The Properties can be similalry spliced open and converted to ImportOptions, ImageOptions, LinkOptions 
                //and CustomLinkText by one of the Get..FromNodeValue methods. 
                //The user will have set these properties (or accepted the defaults) via the application UI which then
                //serializes the value as a property string stored in the node Value string.

                //We need the selected import option to determine how the component file should be processed
                ImportOptions ImportOpt = (ImportOptions)Convert.ToInt32(GetInsertOptionFromNodeValue(DocNode.Value));


                //We process the import according to on the selected import option
                switch (ImportOpt)
                {
                    case ImportOptions.Chunk:
                        {
                            LoadChunk(DocPart, ContentControl, ComponentFile);
                            break;
                        }
                    case ImportOptions.LargeImage:
                        {
                            LoadImage(DocPart, ContentControl, ComponentFile);
                            break;
                        }
                    case ImportOptions.SmallImage:
                        {
                            LoadImage(DocPart, ContentControl, ComponentFile, cStandardThumbWidth);
                            break;
                        }
                    case ImportOptions.SmallSlides:
                    case ImportOptions.SheetsAndChartsAsLargeImages:
                    case ImportOptions.SheetsAndChartsAsSmallImages:
                    case ImportOptions.LargeSlides:
                        {
                            //Extract the image options from the node value which is then passed into the 
                            //LoadSubFolderImages method 
                            ImageOptions ImageOpt = (ImageOptions)Convert.ToInt32(GetImageOptionFromNodeValue(DocNode.Value));
                            LoadSubFolderImages(DocPart, ContentControl, ComponentFile, ImportOpt, ImageOpt);
                            break;
                        }
                    case ImportOptions.Thumbnail:
                        {
                            //Process the component file as a thumbnail image i.e. just insert a thumbnail
                            //of the document - only available when an external thumbnail exists
                            LoadThumbnail(DocPart, ContentControl, ComponentFile);
                            break;
                        }
                    case ImportOptions.Link:
                        {
                            //To process the component file as a hyperlink we need to extract the link option and 
                            //CustomLinkText from the node value and pass these onto the LoadHyperlink method
                            LinkOptions LinkOpt = (LinkOptions)Convert.ToInt32(GetLinkOptionFromNodeValue(DocNode.Value));
                            string CustomLinkText = GetCustomLinkTextFromNodeValue(DocNode.Value);
                            LoadHyperlink(DocPart, ContentControl, ComponentFile, LinkOpt, CustomLinkText);
                            break;
                        }
                    case ImportOptions.Object:
                        {
                            //Process the component file as an object to be embedded
                            LoadObject(DocPart, ContentControl, ComponentFile);
                            break;
                        }

                }
            }
            //Finally, when we've added all content at this insertion point we can remove 
            //the place marker Content Control
            ContentControl.Remove();
        }
    }

    //Just a means of communicating information (usually warnings or errors) the user
    private void RaiseWarning(string WarningMessage)
    {
        labWarning.Text = WarningMessage;
    }

    //The way to clear any warning or error messages
    private void ClearWarning()
    {
        labWarning.Text = string.Empty;
    }

    //This serialses the document selection and import settings in the user has made and stores
    //the value as XML in the returned string.  Basically the XML structure looks like this:
    // <Sections>
    //      <Section SectionName "section name" Expanded "true|false">
    //          <Document ID "ItemIdInt" "ImportOption "ImportOptionValue" ImageOption "ImageOptionValue"
    //                        LinkOption "LinkOptionValue" CustomLinkText "Custom Link Text" 
    //                        SelectedSlides "SelectedSlideIndex1, SelectedSlideIndex2, ..." >
    //-----------------------------------------
    //With corresponding closing tags of course
    public string SerializeTreeView(TreeView treeView)
    {
        XmlWriterSettings settings = new XmlWriterSettings();
        settings.OmitXmlDeclaration = true;
        StringWriter sw = new StringWriter();
        string[] DocNodeProperties;
        string ImportOption;
        string ImageOption;
        string LinkOption;
        string CustomLinkText;
        string ItemIDString;
        string SelectedSlides = string.Empty;
        int ItemID;

        using (XmlWriter writer = XmlWriter.Create(sw, settings))
        {
            writer.WriteStartElement("Sections");
            foreach (TreeNode SectionNode in treeView.Nodes)
            {
                if (SectionNode.ChildNodes.Count > 0)
                {
                    writer.WriteStartElement("Section");
                    writer.WriteAttributeString("SectionName", SectionNode.Value);
                    writer.WriteAttributeString("Expanded", SectionNode.Expanded.ToString());
                    foreach (TreeNode DocNode in SectionNode.ChildNodes)
                    {
                        ItemID = NodeValueToItemId(DocNode.Value);
                        ItemIDString = ItemID.ToString();
                        DocNodeProperties = NodeValueProperties(DocNode.Value);
                        ImportOption = DocNodeProperties[0];
                        ImageOption = DocNodeProperties[1];
                        LinkOption = DocNodeProperties[2];
                        CustomLinkText = DocNodeProperties[3];
                        SPListItem Item = List.GetItemById(ItemID);
                        if (ViewState[Item.UniqueId.ToString()] != null)
                           SelectedSlides = ViewState[Item.UniqueId.ToString()].ToString();

                        writer.WriteStartElement("Document");
                        writer.WriteAttributeString("ID", ItemIDString);
                        writer.WriteAttributeString("ImportOption", ImportOption);
                        writer.WriteAttributeString("ImageOption", ImageOption);
                        writer.WriteAttributeString("LinkOption", LinkOption);
                        writer.WriteAttributeString("CustomLinkText", CustomLinkText);
                        writer.WriteAttributeString("SelectedSlides", SelectedSlides);
                        writer.WriteEndElement();
                    }
                    writer.WriteEndElement();
                }
            }
            writer.WriteEndElement();
            writer.Flush();
            return sw.ToString();
        }
    }

    //Get references to the Selected Node from the view state.  It relies on the
    //tvSelectedNodeChange event to set the view state
    private TreeNode GetSelectedNodeFromViewState()
    {
        string SelectedNodeValuePath = ViewState["SelectedNodeValuePath"].ToString();
        return tvSelected.FindNode(SelectedNodeValuePath);
    }

    //Get references to the Selected Node from the view state.  It relies on the
    //tvSelectedNodeChange event to set the view state
    private TreeNode GetSelectedSectionNodeFromViewState()
    {
        string SelectedNodeValuePath = ViewState["SelectedSectionNodeValuePath"].ToString();
        return tvSelected.FindNode(SelectedNodeValuePath);
    }

    //Takes in an SPListItem which represents a document in the library and configures the controls
    //in the Left Nav Bar areas to display some useful information describing the document, 
    //including a thumbnailimage, where one exists.
    private void ConfigureItemDetails(SPListItem DocItem)
    {
        try
        {
            //If there is no DocItem passed in then just hide the controls and return
            if (DocItem == null)
            {
                lnkDocThumbnail.Visible = false;
                labDocInfo.Visible = false;
                return;
            }
            else  //Make sure the controls are visible and continue
            {
                lnkDocThumbnail.Visible = true;
                labDocInfo.Visible = true;
            }
            SPFile SelectedFile = DocItem.File;

            //Set up the thumbnail control with an image and a location to
            //navigate to when the link is clicked.  This depends entirely on
            //having thumbnail image stored in the special hidden _t and _w
            //folders.  See my blog on Thumbnail Generation in a document library
            lnkDocThumbnail.ImageUrl = GetThumbnailUrl(SelectedFile, "_t");
            lnkDocThumbnail.NavigateUrl = GetThumbnailUrl(SelectedFile, "_w");


            //The above method calls will return a path to the thumbnail if one exists
            //but if one does not exits then a link to suitable graphic in the "/_layouts/images/" folder
            //is returned, in which case we need to disable the onMouseOver and onMouseOut client side events
            //and set a few properties on the thumbnail hyperlink.  The application page contains some in-line
            //code which pops up a Div which contains the large version of the thumbnail as opposesed to the
            //smallversion (_w and _t respectively) so that the user can be sure they are working with the
            //correct file.  If we don't have any thumbnails then clearly we need to deactivate this feature.
            if (lnkDocThumbnail.NavigateUrl.EndsWith(SelectedFile.IconUrl))
            {
                lnkDocThumbnail.Attributes.Remove("onMouseOver");
                lnkDocThumbnail.Attributes.Remove("onMouseOut");
                lnkDocThumbnail.NavigateUrl = string.Empty;
                lnkDocThumbnail.Text = "Document preview does not exist";
                imgPopupPreview.ImageUrl = string.Empty;
                lnkDocThumbnail.BackColor = System.Drawing.Color.Empty;
            }

            //On the otherhand, if we do find thumbnails then just set up the properties as required
            //on the image and hyperlink controls
            else
            {
                lnkDocThumbnail.BackColor = System.Drawing.Color.White;
                imgPopupPreview.ImageUrl = lnkDocThumbnail.NavigateUrl;
                lnkDocThumbnail.Text = lnkDocThumbnail.NavigateUrl;
            }

            //DocInfo takes in a document item and returns an text string which contains useful summary
            //information about the document.  This text is then feed into the labDocInfo label control
            labDocInfo.Text = "<br>" + DocInfo(DocItem);

        }
        catch (Exception ex)
        {
            //If there is some kind or error them pump the message out to the UI via a call to 
            RaiseWarning(ex.Message);
        }
    }

    //Returns the url path to the thumbnail graphic which might exist at in the specified hidden subfolder namely
    //either _t or _w.  If the sub folder does not exist then a url to an appropriate document icon is returned
    private string GetThumbnailUrl(SPFile DocFile, string SubFolderTitle)
    {
        //Assemble the path to where a thumbnail (.png) image file should be in the sub folder
        string ThumbnailPath = Web.Url + "/" +
                               DocFile.Item.ParentList.Title + "/" +
                               SubFolderTitle + "/" +
                               DocFile.Item.ID.ToString() + ".png";

        //If the thumbnail image file of the specified path exists then return the path url
        if (Web.GetFile(ThumbnailPath).Exists)
            return ThumbnailPath;

        //Otherwise an SPFile object has a handy little method which returns the filename part of an icon
        //url according to the file extension of the file which is stored in the  "/_layouts/images/" folder
        else
            return "/_layouts/images/" + DocFile.IconUrl;
    }

    //This is a rather crude method which loops round all nodes in the collection and strips out
    //any highlighting markup tags (defined in the string constants).  This tags are used to 
    //change the node text colour etc. to indicate that the node is selected.  I know that there is
    //a better way to do this, probably by deriving a new class from treenode.  I did try this but
    //on refelection it seems overkill and it also causes type conversion issues so for the time being
    //you're stuck with this rather ugly approach - ever the pragmatist!
    protected void StripSelectedTags(TreeNodeCollection Nodes)
    {
        foreach (TreeNode Node in Nodes)
            Node.Text = Node.Text.Replace(cHighLightOpenTags, string.Empty).
                        Replace(cHighLightCloseTags, string.Empty);
    }

    //Embrace the Text with the highlight open and close tags.
    //Basically put back what the above strips away.
    protected string AddSelectedTags(string Text)
    {
        return cHighLightOpenTags + Text + cHighLightCloseTags;
    }

    //Called when the user selects a different node in the tvSelected treeview control.
    //This treeview has 2 types of nodes.  The root nodes represent document insertion points and are
    //created by using the OpenXML SDK to read the inner content of the master template docx file in
    //search of content controls (SdtElements or derivatives thereof).  Any content control prefixed
    //with a * character is deemed to be an insertion point and so a root node,  representing that insertion
    //point will have been added to this treeview.  The second type of node (those that sit under the root, 
    //insertion point, nodes) represent documents that will be inserted at the insertion point


    //Set up and configure the import profile panel
    private void ConfigureImportProfilePanel(TreeNode SelectedNode)
    {
        string NodeValue = SelectedNode.Value;
        int ItemID = NodeValueToItemId(NodeValue);
        SPListItem DocItem = List.GetItemById(ItemID);

        string Ext = System.IO.Path.GetExtension(DocItem.File.Name).ToLower();

        ddlInsertOption.Items.Clear();

        Boolean HasThumb = HasThumbnail(DocItem);
        Boolean HasImg = HasImages(DocItem);


        //Add custom import options depending on the file type.
        switch (Ext)
        {
            case (".docx"):
            case (".dotx"):
            case (".docm"):
            case (".dotm"):
            case (".rtf"):
            case (".mht"):
            case (".html"):
            case (".xhtml"):
            case (".xml"):
            case (".txt"):
                {
                    AddImportOption("File Contents", ImportOptions.Chunk);
                    //AddImportOption("Embedded Object", ImportOptions.Object);
                    AddImportOption("Hyperlink", ImportOptions.Link);
                    if (HasThumb)
                        AddImportOption("Thumbnail", ImportOptions.Thumbnail);
                    break;
                }
            //case (".doc"):
            //case (".dot"):
            //    {
            //       // AddImportOption("Embedded Object", ImportOptions.Object);
            //        AddImportOption("Hyperlink", ImportOptions.Link);
            //        if (HasThumb)
            //            AddImportOption("Thumbnail", ImportOptions.Thumbnail);
            //        break;
            //    }
            case (".pptx"):
            case (".potx"):
            case (".potm"):
            case (".pptm"):
            case (".ppsx"):
            case (".pot"):
            case (".pps"):
            case (".ppt"):
                {
                    if (HasImg)
                    {
                        AddImportOption("Small Slides", ImportOptions.SmallSlides);
                        AddImportOption("Large Slides", ImportOptions.LargeSlides);
                        LoadImages(tvImagePicker.Nodes, DocItem);
                    }
                    AddImportOption("Embedded Object", ImportOptions.Object);
                    AddImportOption("Hyperlink", ImportOptions.Link);
                    if (HasThumb)
                        AddImportOption("Thumbnail", ImportOptions.Thumbnail);
                    break;
                }
            case (".xlsx"):
            case (".xlst"):
            case (".xlsm"):
            case (".xltm"):
            case (".xlt"):
            case (".xls"):
                {
                    if (HasImg)
                    {
                        AddImportOption("Large Charts & Images", ImportOptions.SheetsAndChartsAsLargeImages);
                        AddImportOption("Small Charts & Images", ImportOptions.SheetsAndChartsAsSmallImages);
                        LoadImages(tvImagePicker.Nodes, DocItem);
                    }
                    AddImportOption("Embedded Object", ImportOptions.Object);
                    AddImportOption("Hyperlink", ImportOptions.Link);
                    if (HasThumb)
                        AddImportOption("Thumbnail", ImportOptions.Thumbnail);
                    break;
                }
            case (".jpg"):
            case (".bmp"):
            case (".png"):
            case (".emf"):
            case (".wmf"):
            case (".gif"):
                {
                    ddlInsertOption.Items.Add(
                        new System.Web.UI.WebControls.ListItem("Large Image", ((int)ImportOptions.LargeImage).ToString()));
                    ddlInsertOption.Items.Add(
                        new System.Web.UI.WebControls.ListItem("Small Image", ((int)ImportOptions.SmallImage).ToString()));
                    break;
                }
            default:
                {
                    AddImportOption("Hyperlink", ImportOptions.Link);
                    if (HasThumb)
                        AddImportOption("Thumbnail", ImportOptions.Thumbnail);
                    break;
                }
        }

        //If the stored property values are not the "0" default then try to restore the
        //selected values to the ddl controls
        string InsertOptionValue = GetInsertOptionFromNodeValue(SelectedNode.Value);

        if (InsertOptionValue != "0")
            try { ddlInsertOption.SelectedValue = InsertOptionValue; }
            catch { }

        string ImageOptionValue = GetImageOptionFromNodeValue(SelectedNode.Value);
        try { rblSlideOptions.SelectedValue = ImageOptionValue; }
        catch { }

        string LinkOptionValue = GetLinkOptionFromNodeValue(SelectedNode.Value);
        try
        {
            rblLinkOptions.SelectedValue = LinkOptionValue;
            LinkOptions LinkOpt = (LinkOptions)Convert.ToInt32(LinkOptionValue);
            if (LinkOpt == LinkOptions.Custom)
                tbCustomLinkText.Enabled = true;
            else
                tbCustomLinkText.Enabled = false;
        }
        catch { }

        //Set the text in the CustomLinkText text box control
        string CustomLinkText = GetCustomLinkTextFromNodeValue(SelectedNode.Value);
        if (CustomLinkText == string.Empty)
            tbCustomLinkText.Text = cDefaultLinkText;
        else
            tbCustomLinkText.Text = CustomLinkText;

        ImportOptions ImportOpt = (ImportOptions)Convert.ToInt32(InsertOptionValue);
        ImageOptions ImageOpt = (ImageOptions)Convert.ToInt32(ImageOptionValue);
        SetControls(ImportOpt, ImageOpt, HasImg, DocItem);
    }

    //Just a little helper method which adds a new list item to the ddlInsertOption control with
    //text Text and enum ImportOption value as a string in the list item Value property
    private void AddImportOption(string Text, ImportOptions ImportOption)
    {
        ddlInsertOption.Items.Add(new
            System.Web.UI.WebControls.ListItem(Text, ((int)ImportOption).ToString()));
    }

    //This method configures what controls are displayed in the UI according to selected import option and
    //user settings
    private void SetControls(ImportOptions ImportOpt, ImageOptions ImageOpt, bool HasImg, SPListItem Item)
    {
        //Hide everthing that is not common to all and then switch on just whichever controls are needed
        rblSlideOptions.Visible = false;
        labOptions.Visible = false;
        panImagePicker.Visible = false;
        panLinkOptions.Visible = false;

        switch (ImportOpt)
        {
            case ImportOptions.SmallSlides:
            case ImportOptions.SmallSlidesWithNotes:
            case ImportOptions.TablesAndCharts:
            case ImportOptions.LargeSlides:
            case ImportOptions.LargeSlidesWithNotes:
            case ImportOptions.SheetsAndChartsAsLargeImages:
            case ImportOptions.SheetsAndChartsAsSmallImages:
                {
                    if (HasImg)
                    {
                        rblSlideOptions.Visible = true;
                        labOptions.Visible = true;
                        panImagePicker.Visible = true;
                        if (ImageOpt == ImageOptions.All)
                            tvImagePicker.ShowCheckBoxes = TreeNodeTypes.None;
                        else
                            tvImagePicker.ShowCheckBoxes = TreeNodeTypes.Root;
                        cbSelectAll.Visible = (tvImagePicker.ShowCheckBoxes == TreeNodeTypes.Root);
                        //Restore the selected nodes from view state
                        if (tvImagePicker.ShowCheckBoxes == TreeNodeTypes.Root)
                        {
                            string Key = Item.UniqueId.ToString();
                            if (ViewState[Key] != null)
                                try
                                {
                                    string[] CheckedItemsIndices = ViewState[Key].ToString().Split(',');
                                    foreach (string ItemIndexString in CheckedItemsIndices)
                                        tvImagePicker.Nodes[Convert.ToInt32(ItemIndexString) - 1].Checked = true;
                                }
                                catch { }
                        }
                    }
                    break;
                }
          case ImportOptions.Link:
                {
                    labOptions.Visible = true;
                    panLinkOptions.Visible = true;
                    break;
                }
        }
    }

    //Checks to see if a thumbnail image exists
    private Boolean HasThumbnail(SPListItem DocItem)
    {
        SPFolder RootFolder = DocItem.ParentList.RootFolder;
        string ThumbPath = Web.Url + "/" + RootFolder.Url + "/_w/" + DocItem.ID.ToString() + ".png";
        return Web.GetFile(ThumbPath).Exists;
    }

    //Checks to see if images exist
    private Boolean HasImages(SPListItem DocItem)
    {
        SPFolder RootFolder = DocItem.ParentList.RootFolder;
        string ThumbPath = Web.Url + "/" + RootFolder.Url + "/_w/" + DocItem.ID.ToString();
        return Web.GetFolder(ThumbPath).Exists;
    }

    //This method loads PowerPoint Slide and Excel Chart images into the tvImagePicker treeview control.
    //This is only ever applicable if the thumbnails are available.
    private void LoadImages(TreeNodeCollection Nodes, SPListItem DocItem)
    {
        Nodes.Clear();
        SPFile DocFile = DocItem.File;
        SPList List = DocItem.ParentList;
        SPFolder RootFolder = List.RootFolder;
        string SubFolder = DocItem.ID.ToString();
        string _tSubFolderPath = Web.Url + "/" + RootFolder.Url + "/_t/" + SubFolder + "/";
        string _wSubFolderPath = Web.Url + "/" + RootFolder.Url + "/_w/" + SubFolder + "/";

        SPFolder _t;
        TreeNode NewRootNode;
        TreeNode NewImageNode;
        string RootNodeTitle = string.Empty;
        if (!Web.GetFolder(_tSubFolderPath).Exists)
            return;
        else
        {
            _t = Web.GetFolder(_tSubFolderPath);

            System.Collections.SortedList ImageFiles = new System.Collections.SortedList();

            string ImageKey = string.Empty;

            foreach (SPFile ImageFile in _t.Files)
            {
                try

                {
                    if (ImageFile.Name.StartsWith("Slide "))
                        ImageKey = ImageFile.Name.Replace("Slide ", string.Empty).Replace(".jpg", string.Empty);
                    else if (ImageFile.Name.StartsWith("Sheet "))
                        ImageKey = "a" + ImageFile.Name.Replace("Sheet ", string.Empty).Replace(".jpg", string.Empty);
                    else if (ImageFile.Name.StartsWith("Chart "))
                        ImageKey = "b" + ImageFile.Name.Replace("Chart ", string.Empty).Replace(".jpg", string.Empty);
                    ImageFiles.Add(ImageKey, ImageFile);
                }
                catch { }
            }
            string NodeText;
            string ImageIndex = string.Empty;
            for (int i = 0; i < ImageFiles.Count; i++)
            {
                SPFile ImageFile = (SPFile)ImageFiles.GetByIndex(i);

                //TODO insert the Title as the node text instead of the file name
                //try
                //{
                //    RootNodeTitle = ImageFile.Item["Title"].ToString(); 
                //}
                //catch {}
                //if (RootNodeTitle == string.Empty)
                //    RootNodeTitle = System.IO.Path.GetFileNameWithoutExtension(ImageFile.Name);
                //NewRootNode = new TreeNode(RootNodeTitle, _tSubFolderPath + ImageFile.Name);

                //Start out with just the file name (less extension)
                NodeText = System.IO.Path.GetFileNameWithoutExtension(ImageFile.Name);

                if (NodeText.StartsWith("Slide "))
                    ImageIndex = ImageFile.Name.Replace("Slide ", string.Empty).Replace(".jpg", string.Empty);
                else if (NodeText.StartsWith("Sheet "))
                    ImageIndex = ImageFile.Name.Replace("Sheet ", string.Empty).Replace(".jpg", string.Empty);
                else if (NodeText.StartsWith("Chart "))
                    ImageIndex = ImageFile.Name.Replace("Chart ", string.Empty).Replace(".jpg", string.Empty);

                NodeText = NodeText.Replace(ImageIndex, ImageIndex.TrimStart('0'));
                
                NewRootNode = new TreeNode(NodeText);
                NewRootNode.NavigateUrl = _wSubFolderPath + ImageFile.Name;
                NewRootNode.Target = "_blank";
                Nodes.Add(NewRootNode);
                NewImageNode = new TreeNode();
                NewImageNode.ImageUrl = _tSubFolderPath + ImageFile.Name;
                NewImageNode.NavigateUrl = _wSubFolderPath + ImageFile.Name;
                NewImageNode.Target = "_blank";
                NewRootNode.ChildNodes.Add(NewImageNode);
            }
        }
    }

    
    //Sets the status (enabled/disabled) of the MoveUp and MoveDown buttons depending on which node is passed in
    protected void SetMoveButtons(TreeNode SelectedNode)
    {
        if (SelectedNode.Depth == 0)
        {
            SetMoveUpButtons(false);
            SetMoveDownButtons(false);
        }
        else
        {
            TreeNode ParentNode = SelectedNode.Parent;
            //Set the Move Up buttons
            if (ParentNode == tvSelected.Nodes[0])
                if (SelectedNode == ParentNode.ChildNodes[0])
                    SetMoveUpButtons(false);
                else
                    SetMoveUpButtons(true);
            else
                SetMoveUpButtons(true);
            //Set the Move Down buttons
            if (ParentNode == tvSelected.Nodes[tvSelected.Nodes.Count - 1])
                if (SelectedNode == ParentNode.ChildNodes[ParentNode.ChildNodes.Count - 1])
                    SetMoveDownButtons(false);
                else
                    SetMoveDownButtons(true);
            else
                SetMoveDownButtons(true);
        }
    }

    //Enables or disables the MoveUp buttons
    protected void SetMoveUpButtons(Boolean Enabled)
    {
        ibtnMoveUp.Enabled = Enabled;
        lbtnMoveUp.Enabled = Enabled;
        if (Enabled)
            ibtnMoveUp.ImageUrl = cArrupa;
        else
            ibtnMoveUp.ImageUrl = cArrupi;
    }

    //Enables or disables the MoveDown buttons
    protected void SetMoveDownButtons(Boolean Enabled)
    {
        ibtnMoveDown.Enabled = Enabled;
        lbtnMoveDown.Enabled = Enabled;
        if (Enabled)
            ibtnMoveDown.ImageUrl = cArrdowna;
        else
            ibtnMoveDown.ImageUrl = cArrdowni;
    }

    //Sets the status (enabled/disabled) of the Left button depending on the value passed in to SelectedNode
    protected void SetLeftButton(TreeNode SelectedNode)
    {
        if ((SelectedNode == null) || (SelectedNode.Depth == 0))
            btnLeft.Enabled = false;
        else
            btnLeft.Enabled = true;
    }

    //Used to create a clone of a treenode with all properties intact
    private TreeNode CloneNode(TreeNode Node)
    {
        TreeNode Clone = new TreeNode(Node.Text, Node.Value, Node.ImageUrl);
        Clone.ToolTip = Node.ToolTip;
        return Clone;
    }

    //Used to copy an existing treenode which represents a document item but with some of the key properties
    //set to default values rather than what is specified in the source node (Node).
    private TreeNode CopyNode(TreeNode Node)
    {
        int DocId = NodeValueToItemId(Node.Value);
        SPListItem Item = List.GetItemById(DocId);
        string ext = System.IO.Path.GetExtension(Item.File.Name);
        ImportOptions DefaultImportOpt = DefaultImportOption(ext);
        string CopyNodeValue =
            ItemIdToNodeValue(DocId, DefaultImportOpt,
                        ImageOptions.All, LinkOptions.Filename, cDefaultLinkText);
        TreeNode CopyNode = new TreeNode(Node.Text, CopyNodeValue, Node.ImageUrl);
        CopyNode.ToolTip = Node.ToolTip;
        return CopyNode;
    }

    //Constructs the tvSelected treeview control based on items and their property settings serialised when the
    //profile was saved and stored as XML in the "DocComponents" property of the master document template item.
    void InitialiseSelectedTreeview(TreeNodeCollection Nodes, SPFile File)
    {
        string SelectedComponents = string.Empty;
        TreeNode SectionNode;

        Stream FileStream = File.OpenBinaryStream(SPOpenBinaryOptions.SkipVirusScan);
        using (WordprocessingDocument MasterDoc = WordprocessingDocument.Open(FileStream, true))
        {

            MainDocumentPart MainDocPart = MasterDoc.MainDocumentPart;

            //This list will contain all Content Controls (SdtElement) found in the headers, footers and
            //main body of the document.  We add the header controls first, followed by the main body and
            //finally the footers, just because that order normally makes sense in the UI.

            //TODO: There are other document parts we could look in such as Glossary and Comment parts but we'll
            //save that for another day.  Also it might make more sense to break the search up to add Content
            //Controls on a document section by section basis because each document section might have a unique
            //header and/or footer.  Again, a job for another rainy day.
            List<SdtElement> ContentControls = new List<SdtElement>();

            //Pound through the headers recusing and adding to the list of Content Controls as we go
            foreach (HeaderPart HeaderPart in MasterDoc.MainDocumentPart.HeaderParts)
                ContentControls.AddRange(RecurseForContentControls(HeaderPart.Header.Elements()));

            //Do the same for the document body
            ContentControls.AddRange(RecurseForContentControls(MainDocPart.Document.Body.Elements()));

            //And finally through the footers
            foreach (FooterPart FooterPart in MasterDoc.MainDocumentPart.FooterParts)
                ContentControls.AddRange(RecurseForContentControls(FooterPart.Footer.Elements()));

            //For each Content Control that starts with an '*' character create a node in the passed in treenode
            //collection, which is actually the root nodes collection of the tvSelected treeview control.
            //Each of these nodes represents an insertion point within the master template document and is where
            //we need to insert one or more component files in the output document, which starts out life as
            //being a copy of the master.  The profile (i.e. the stored set of component documents complete with
            //their import properties) for each section node is stored as an XML element in the "DocComponents"
            //field of the master template document item.  So we need to consume this XML and build the treeview
            //accordingly

            //TODO: I've called these insertion point nodes SectionNodes which on reflection might be a bit
            //confusing as they do not represent sections within the Word document: refactor.
            foreach (SdtElement ContentControl in ContentControls)
                if (ContentControl.InnerText.StartsWith("*"))
                {
                    SectionNode = new TreeNode(ContentControl.InnerText.TrimStart('*'), ContentControl.InnerText);
                    SectionNode.SelectAction = TreeNodeSelectAction.Select;
                    Nodes.Add(SectionNode);

                    //Get the XML text from the DocComponents hidden field of the master document template item 
                    string xmlProfile = string.Empty;
                    try
                    {
                        xmlProfile = File.Item["DocComponents"].ToString();
                    }
                    catch { }

                    //Assumin that there is some XML to process...
                    if (!string.IsNullOrEmpty(xmlProfile))
                    {
                        //Create some useful objects to help process and parse the XML
                        StringReader sr = new StringReader(xmlProfile);
                        XPathDocument xpd = new XPathDocument(sr);
                        XPathNavigator xpn = xpd.CreateNavigator();

                        //Find the XML fragment that represents the current insertion point section
                        string xpq = string.Format("/Sections/Section[@SectionName=\"{0}\"]", SectionNode.Value);
                        XPathNavigator xpNode = xpn.SelectSingleNode(xpq);
                        TreeNode DocNode;

                        //If the XML contains an entry for this insertion point section then we need to add the
                        //document nodes beneath it
                        if (xpNode != null)
                        {
                            //Whilst we are here we can restore the Expanded state of the section node
                            SectionNode.Expanded = Convert.ToBoolean(xpNode.GetAttribute("Expanded", string.Empty));

                            //Move to the first child node of the section insertion point node
                            if (xpNode.MoveToFirstChild())
                            {
                                //Read the attributes which represent the document and import settings etc. set by
                                //the user and which were serliaised out as this XML when the profile was saved.

                                //First get the list ID of the document in the library the document child node will
                                //represent.
                                string DocID = xpNode.GetAttribute("ID", string.Empty);
                                int ListItemID = Convert.ToInt32(DocID);

                                //The look for the ImportOption attribute which specifies how the document should
                                //be processes when the output document is built i.e. as Chunk, as Object, as Link etc.
                                string ImportOption = xpNode.GetAttribute("ImportOption", string.Empty);
                                ImportOptions ImportOpt = ImportOptions.Unassigned;
                                try { ImportOpt = (ImportOptions)Convert.ToInt32(ImportOption); }
                                catch { }

                                //The ImageOption attribute is either All (default) or Selected and is used to
                                //configure the UI such that when the value is Selected the user can pick from the
                                //available set of images.  This is only used when processing PowerPoint slides 
                                //which have been stored as individual images in the hidden _t and _w sub folders of
                                //a library - see blog posts on Generating Thumbnails in a document library.
                                string ImageOption = xpNode.GetAttribute("ImageOption", string.Empty);
                                ImageOptions ImageOpt = ImageOptions.All;
                                try { ImageOpt = (ImageOptions)Convert.ToInt32(ImageOption); }
                                catch { }

                                //The LinkOption attribute determines the hyperlink text of a component file 
                                //(only applied when ImportOpt set to Link of course).  Link options for the text
                                //are to use the file name (default), the Title of the SPList item, the complete
                                //Url to the document or some custom text (which may be stored in the 
                                //"CustomLinkText" attribute
                                string LinkOption = xpNode.GetAttribute("LinkOption", string.Empty);
                                LinkOptions LinkOpt = LinkOptions.Filename;
                                try
                                { LinkOpt = (LinkOptions)Convert.ToInt32(LinkOption); }
                                catch { }

                                //Where the LinkOption specified to use custom text for the hyperlink then the
                                //CustomLinkText attribute is where this custom text has been stored
                                string CustomLinkText = xpNode.GetAttribute("CustomLinkText", string.Empty);

                                //When a the component file is a PowerPoint presentation then the user may have
                                //chosen a subset selection off slides to import
                                string SelectedSlides = xpNode.GetAttribute("SelectedSlides", string.Empty);
                                if (SelectedSlides != string.Empty)
                                {
                                    SPListItem Item = List.GetItemById(ListItemID);
                                    string Key = Item.UniqueId.ToString();
                                    ViewState.Add(Key, SelectedSlides);
                                }
                                
                                //Create a new treenode that represents a component document to be inserted at this
                                //insertion point in the output document.  The AddDocNode method will take care of 
                                //converting the serialised attributes into the node Value string etc.
                                DocNode = AddDocNode(SectionNode.ChildNodes, List, ListItemID,
                                    ImportOpt, ImageOpt, LinkOpt, CustomLinkText, true);

                                //The SelectedComponents string will be used to store in ViewState the ID of all 
                                //component documents already selected i.e. added to the tvSelected treenodes 
                                //collection.  Note that because we will later need to parse this SelectedComponents
                                //string we need delimiter characters both font and back; ~ and ; respectively.  This
                                //is because if we seached in the string for just "12;" we might find both '12;' and 
                                //'112;' whereas if we search for '~12;' we would not find '112;', got it?
                                SelectedComponents += "~" + DocID + ";";

                                //Now move the next XML node (if there is one) and repeat the above process until we
                                //run out of child XML nodes.
                                while (xpNode.MoveToNext())
                                {
                                    DocID = xpNode.GetAttribute("ID", string.Empty);

                                    ImportOption = xpNode.GetAttribute("ImportOption", string.Empty);
                                    try { ImportOpt = (ImportOptions)Convert.ToInt32(ImportOption); }
                                    catch { }


                                    ImageOption = xpNode.GetAttribute("ImageOption", string.Empty);
                                    try { ImageOpt = (ImageOptions)Convert.ToInt32(ImageOption); }
                                    catch { }

                                    LinkOption = xpNode.GetAttribute("LinkOption", string.Empty);
                                    try { LinkOpt = (LinkOptions)Convert.ToInt32(LinkOption); }
                                    catch { }

                                    CustomLinkText = xpNode.GetAttribute("CustomLinkText", string.Empty);

                                    ListItemID = Convert.ToInt32(DocID);
                                    DocNode = AddDocNode(SectionNode.ChildNodes, List, ListItemID,
                                                  ImportOpt, ImageOpt, LinkOpt, CustomLinkText, true);

                                    SelectedSlides = xpNode.GetAttribute("SelectedSlides", string.Empty);
                                    if (SelectedSlides != string.Empty)
                                    {
                                        SPListItem Item = List.GetItemById(ListItemID);
                                        string Key = Item.UniqueId.ToString();
                                        ViewState.Add(Key, SelectedSlides);
                                    }

                                    SelectedComponents += "~" + DocNode.Value + ";";
                                }
                            }
                        }
                    }
                }
        }

        //Now we've retored the treeview we can save out the list of documents all ready selected to ViewState.
        //We want this information becuase we might wish to exclude this files from showing up in the
        //tvLibraryBrowser treeview control.  This in turn depends on whether the cbExcludeAdded checkbox is checked
        if (ViewState["SelectedComponts"] == null)
            ViewState.Add("SelectedComponents", SelectedComponents);
        else
            ViewState["SelectedComponents"] = SelectedComponents;

        //Restore the list item that was saved as the metadata source
        //(if any was or can be found) need to try/catch in case of an
        //empty or invalid field value
        try
        {
            if (!string.IsNullOrEmpty(File.Item["MetaDataSource"].ToString()))
                ddlMetaDataSource.SelectedValue = File.Item["MetaDataSource"].ToString();
        }
        catch (Exception ex)
        {
            RaiseWarning(ex.Message);
        }

        //Finally scan through the tvSelected treeview and add up the number of component files in each insertion
        //point section and at that as bold text to the node text of each of these section nodes.
        SetNodeComponentCount(Nodes);
    }

    //This method digs down through the structure of the document part adding SdtElements to the list
    //that is ultimately returnedas it goes.  This need to be recursive because not all SdtElement
    //objects are necessarily at the same level in the document part as some might be buried in tables etc.
    private List<SdtElement> RecurseForContentControls(IEnumerable<OpenXmlElement> Elements)
    {
        List<SdtElement> ContentControlsList = new List<SdtElement>();

        foreach (OpenXmlElement Element in Elements)
        {
            if (Element is SdtElement)
                ContentControlsList.Add((SdtElement)Element);
            else if (Element.Elements().Count<OpenXmlElement>() > 0)
            {
                ContentControlsList.AddRange(RecurseForContentControls(Element.Elements()));
            }
        }

        return ContentControlsList;
    }

    //Adds a treenode to the passed in node collection which represents a document in the library
    protected TreeNode AddDocNode(TreeNodeCollection Nodes, SPList List, int ListItemID,
                                  ImportOptions ImportOption, ImageOptions ImageOption,
                                  LinkOptions LinkOption, string CustomLinkText, Boolean AddAsParent)
    {
        TreeNode NewNode = null;
        try
        {
            //Get the item, item file and the name of the file
            SPListItem Item = List.GetItemById(ListItemID);
            SPFile File = Item.File;
            string NodeText = Item.File.Name;

            //If the filename is too long then chop it's length
            if (NodeText.Length > cMaxNodeTextLength)
                NodeText = NodeText.Substring(0, cMaxNodeTextLength - 1) + "...";

            //Assmble the value string that contains the properties of this node as set by the user
            string NodeValue =
                ItemIdToNodeValue(Item.ID, ImportOption, ImageOption, LinkOption, CustomLinkText);

            //Create the new document node itself using the file name (possibly chopped in length) as the
            //node's Text, the properties of the node which determine how the document represented by the node will be
            //processed in the node's Value property and a url to the SharePoint icon that indicates file type  
            NewNode = new TreeNode(NodeText, NodeValue, "/_layouts/images/" + Item.File.IconUrl);

            //Simply allow the node to be selected when clicked by the user
            NewNode.SelectAction = TreeNodeSelectAction.Select;

            //Get some useful text that describes the document based on the properties and use that as the tooltip
            NewNode.ToolTip = NodeTooltip(Item, CurrentView);

            //Node just add the node to the passed in collection
            Nodes.Add(NewNode);

            //If the AddAsParent parameter is true then this means that the file should be treated as a potential
            //source that determines the content type and initial meta-data properties of the output compound 
            //document in which case we need to add a new item, representing the file, to the ddlMetaDataSource 
            //drop down list control
            if (AddAsParent)
                AddMetaDataSource(ddlMetaDataSource, Item);
        }
        catch { }
        return NewNode;
    }

    //Adds a list drop down list item which represents an SPListItem to the ddlControl.
    //The text of the list item is set to be the file name of the SPFile property of the 
    //SPListItem and the Value is set to be the UniqueID of the SPListItem as a string
    private void AddMetaDataSource(DropDownList ddlControl, SPListItem Item)
    {
        string Text = Item.File.Name;
        string Value = Item.UniqueId.ToString();
        System.Web.UI.WebControls.ListItem NewItem =
            new System.Web.UI.WebControls.ListItem(Text, Value);
        ddlControl.Items.Add(NewItem);
    }

    //Removes from the ddlControl the drop down list item that represents the SPListItem
    private void RemoveMetaDataSource(ref DropDownList ddlControl, SPListItem Item)
    {
        System.Web.UI.WebControls.ListItem MetaDataSourceItem =
              ddlControl.Items.FindByValue(Item.UniqueId.ToString());
        if (MetaDataSourceItem != null)
        {
            if (ddlControl.SelectedValue == MetaDataSourceItem.Value)
                ddlControl.SelectedIndex = 0;
            ddlControl.Items.Remove(MetaDataSourceItem);
        }
    }

    //Returns the node value string by assembling the key properties with delimiter characters
    //so that they can be parsed and retreived
    private string ItemIdToNodeValue(int ItemId, ImportOptions ImportOption,
                                     ImageOptions ImageOption, LinkOptions LinkOption,
                                     string CustomLinkText)
    {
        int ImportOptionValue = (int)ImportOption;
        int ImageOptionValue = (int)ImageOption;
        int LinkOptionValue = (int)LinkOption;

        return ItemId.ToString() + "#" + Guid.NewGuid().ToString() +
           "#" + ImportOptionValue.ToString() + "|" +
                 ImageOptionValue.ToString() + "|" +
                 LinkOptionValue.ToString() + "|" +
                 CustomLinkText;
    }

    //Returns the ItemId part of the NodeValue string
    private int NodeValueToItemId(string NodeValue)
    {
        return Convert.ToInt32(NodeValue.Split('#')[0]);
    }

    //Returns the properties in the NodeValue string as an array of values
    private string[] NodeValueProperties(string NodeValue)
    {
        return NodeValue.Split('#')[2].Split('|');
    }

    //Returns the InsertOption value (as a string) from the NodeValue text passed in
    private string GetInsertOptionFromNodeValue(string NodeValue)
    {
        return NodeValue.Split('#')[2].Split('|')[0];
    }

    //Returns the ImageOption value (as a string) from the NodeValue text passed in
    private string GetImageOptionFromNodeValue(string NodeValue)
    {
        return NodeValue.Split('#')[2].Split('|')[1];
    }

    //Returns the LinkOption value (as a string) from the NodeValue text passed in
    private string GetLinkOptionFromNodeValue(string NodeValue)
    {
        return NodeValue.Split('#')[2].Split('|')[2];
    }

    //Returns the CustomLinkText value from the NodeValue text passed in
    private string GetCustomLinkTextFromNodeValue(string NodeValue)
    {
        return NodeValue.Split('#')[2].Split('|')[3];
    }

    //Appends the number of child nodes under each section insertion point node 
    void SetNodeComponentCount(TreeNodeCollection Nodes)
    {
        foreach (TreeNode Node in Nodes)
            Node.Text = Node.Value.TrimStart('*') +
                " <b>(" + Node.ChildNodes.Count.ToString() + ")</b>";
    }

    #endregion

    #region OpenXML SDK Load and Supporting Methods

        #region Load Methods

    //Loads a chuck from another document into where the content control is located    
    private void LoadChunk(OpenXmlPart DocPart, SdtElement ContentControl, SPFile ComponentFile)
    {
        AlternativeFormatImportPart chunk = null;
        string altChunkId = string.Empty;
        altChunkId = "Component-" + ComponentFile.Item.ID.ToString();
        string Ext = System.IO.Path.GetExtension(ComponentFile.Name).ToLower();
        AlternativeFormatImportPartType ImportFormat = AlternativeFormatImportPartType.WordprocessingML;

        switch (Ext)
        {
            case ".docx":
                {
                    ImportFormat = AlternativeFormatImportPartType.WordprocessingML;
                    break;
                }
            case ".dotx":
                {
                    ImportFormat = AlternativeFormatImportPartType.OfficeWordTemplate;
                    break;
                }
            case ".docm":
                {
                    ImportFormat = AlternativeFormatImportPartType.OfficeWordMacroEnabled;
                    break;
                }
            case ".dotm":
                {
                    ImportFormat = AlternativeFormatImportPartType.OfficeWordMacroEnabledTemplate;
                    break;
                }
            case ".rtf":
                {
                    ImportFormat = AlternativeFormatImportPartType.Rtf;
                    break;
                }
            case ".txt":
                {
                    ImportFormat = AlternativeFormatImportPartType.TextPlain;
                    break;
                }
            case ".html":
                {
                    ImportFormat = AlternativeFormatImportPartType.Html;
                    break;
                }
            case ".mht":
                {
                    ImportFormat = AlternativeFormatImportPartType.Mht;
                    break;
                }
            case ".xhtl":
                {
                    ImportFormat = AlternativeFormatImportPartType.Xhtml;
                    break;
                }
            case ".xml":
                {
                    ImportFormat = AlternativeFormatImportPartType.Xml;
                    break;
                }
        }

        if (DocPart is MainDocumentPart)
        {
            MainDocumentPart MainDocPart = (MainDocumentPart)DocPart;
            chunk = MainDocPart.AddAlternativeFormatImportPart(ImportFormat, altChunkId);
        }
        else if (DocPart is HeaderPart)
        {
            HeaderPart HeaderPart = (HeaderPart)DocPart;
            chunk = HeaderPart.AddAlternativeFormatImportPart(ImportFormat, altChunkId);
        }
        else if (DocPart is FooterPart)
        {
            FooterPart FooterPart = (FooterPart)DocPart;
            chunk = FooterPart.AddAlternativeFormatImportPart(ImportFormat, altChunkId);
        }

        chunk.FeedData(ComponentFile.OpenBinaryStream());
        AltChunk altChunk = new AltChunk();
        altChunk.Id = altChunkId;
        OpenXmlElement parent = ContentControl.Parent;

        //If the Content control is table cell based i.e. SdtCell then removing the control
        //also removes the cell so we need to provide a replacement standard table cell
        if (ContentControl is SdtCell)
        {
            DocumentFormat.OpenXml.Wordprocessing.TableCell NewCell = GenerateTableCell();
            ContentControl.InsertAfterSelf(NewCell);
        }
        parent.InsertAfter(altChunk, ContentControl);
    }

    //I tried the method demonstrated in the SDK to import an object into the package and the add a 
    //hosting paragraph complete with activation image part.  This worked just fine so long as you
    //only added one object.  When you attempt to add multiple objects then the first is embedded as
    //expected but subsequent embedding attempts fails for reasons unknown.  The image part is added
    //sucessfully but not the object.

    //To resolve this required a bit of lateral thinking.  Basically the strategy is to create a 
    //temporary file in docx format, embedd the component file as an object in that file, then use the
    //chunk approach to combine the temporary file to the target output file and finally delete the 
    //temporary file (which happens automaticaly on file close).

    //I did try to do this entirely in memory using memory streams but failed miserably.  The only way
    //I could get this to work was with the temporary file approach.  It's a bit convoluted to do things 
    //this way but I guess that's what you have to live with when working with a CTP.
    
    private void LoadObject(OpenXmlPart DocPart, SdtElement ContentControl, SPFile ComponentFile)
    {
        //Extract the file extension of the Component File
        string Ext = System.IO.Path.GetExtension(ComponentFile.Name).ToLower();


        string ProgId = string.Empty;
        string ContentTypeId = string.Empty;
        SetProgAndContentTypeId(Ext, ref ProgId, ref ContentTypeId);


        //If the ProgId or the ContentTypeId are empty then we don't know how to deal with this
        //file format so simple return
        if ((ProgId == string.Empty) || (ContentTypeId == string.Empty)) return;

        //Instantiate a new AutoGeneratedDocx instance.  The code to do this was determined by using the
        //document reflector which comes with the Open XML SDK.  The class is declared in the class file
        //AutoGeneratedDocx.cs
        SDKGeneratedCode.AutoGeneratedDocx AutoGeneratedDocx = new SDKGeneratedCode.AutoGeneratedDocx();

        //Create a temporary file on the server
        string TempFilePath = System.IO.Path.GetTempFileName();

        //Create the autogenerated package in the temporary file
        AutoGeneratedDocx.CreateDocxPackage(TempFilePath);

        
        //Now open the temporary file with the SDK and access the main document part
        using (WordprocessingDocument NewWordMLDoc = WordprocessingDocument.Open(TempFilePath, true))
        {
            MainDocumentPart TempMainDocPart = NewWordMLDoc.MainDocumentPart;

            //Add a new embedded package part to the temp file with the ContentTypeId having already
            //been determined from the extension of the ComponentFile

            string ObjectPartId;

            if (ObjectOrPackage(Ext) == ImportObjectAs.Package)
            {
                EmbeddedPackagePart embeddedPackagePart =
                        TempMainDocPart.AddEmbeddedPackagePart(ContentTypeId);
                //Feed the embedded package part with the binary from the ComponentFile
                embeddedPackagePart.FeedData(ComponentFile.OpenBinaryStream(SPOpenBinaryOptions.SkipVirusScan));
                //Get a relationship reference to the newly embedded package part
                ObjectPartId = TempMainDocPart.GetIdOfPart(embeddedPackagePart);
            }
            else
            {
                EmbeddedObjectPart embeddedObjectPart =
                    TempMainDocPart.AddEmbeddedObjectPart(ContentTypeId);
                //Feed the embedded object part with the binary from the ComponentFile
                embeddedObjectPart.FeedData(ComponentFile.OpenBinaryStream(SPOpenBinaryOptions.SkipVirusScan));
                //Get a relationship reference to the newly embedded object part
                ObjectPartId = TempMainDocPart.GetIdOfPart(embeddedObjectPart);
            }

            //Next we need to add an image part to the temporary document which when clicked will
            //activate the object behind
            string ImagePartId = string.Empty;
            ImagePart ImagePart = null;

            Boolean ThumbnailExists = false;
            string ThumbnailPath = string.Empty;

            //Checkto see if both the thumbnail folder and file exist
            if (Web.GetFolder(Web.Url + "/" + ComponentFile.Item.ParentList.RootFolder.Url + "/_w").Exists)
            {   
                SPFolder TargetFolder = ComponentFile.Item.ParentList.RootFolder.SubFolders["_w"];
                ThumbnailPath = Web.Url + "/" + TargetFolder.Url + "/" + ComponentFile.Item.ID.ToString() + ".png";
                if (Web.GetFile(ThumbnailPath).Exists)
                {
                    ThumbnailExists = true;
                }
            }

            //If a thumbnail exists then we can add that as an image part to the temporary document.  If a
            //thumbnail is not found then use a default object image which has been added as an embedded
            //resource within the assembly.  In this way we are guarenteed to have a valid image.
            if (ThumbnailExists)
            {
                ImagePart = TempMainDocPart.AddImagePart(ImagePartType.Png);
                SPFile ThumbnailFile = Web.GetFile(ThumbnailPath);
                ImagePart.FeedData(ThumbnailFile.OpenBinaryStream());
            }
            else
            {
                ImagePart = TempMainDocPart.AddImagePart(ImagePartType.Jpeg);
                Stream DefaultImageStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("DocFusion.ActivateObject.jpg");
                ImagePart.FeedData(DefaultImageStream);
            }


            //Get a relationship id reference to the image part
            ImagePartId = TempMainDocPart.GetIdOfPart(ImagePart);

            //Generate a new paragraph passing in the relationship id references to both the embedded
            //object and the image part used to activate the object, along with the ProgId which is
            //derived from the extension of the ComponentFile.  The code for GenerateParagraph was based
            //on the code reflected by the Document Refelector tool which ships with the Open XML SDK
            DocumentFormat.OpenXml.Wordprocessing.Paragraph NewPara =
                GenerateObjectParagraph(ImagePartId, ObjectPartId, ProgId);

            //The code used to generate the temporary file comes with a single paragraph.  We need to get
            //a reference to this paragraph so we can add a our newly created paragraph just after it.
            DocumentFormat.OpenXml.Wordprocessing.Paragraph FirstPara =
              TempMainDocPart.Document.Body.GetFirstChild<DocumentFormat.OpenXml.Wordprocessing.Paragraph>();
            FirstPara.InsertAfterSelf(NewPara);

            //Close down the temporary file, which also closes all/any open streams
            NewWordMLDoc.Close();



            //Now we reopen the temporary file in a new file stream in Read mode but with the FileOption
            //DeleteOnClose which means that when we close the file it will be deleted.  We put this in a
            //try..finally block just to make sure the temporary file gets closed and therefore deleted
            FileStream FS = null;
            try
            {
                //Open the temporary file in a file stream object
                FS = new FileStream(TempFilePath, FileMode.Open, FileAccess.Read,
                                FileShare.Read, Int16.MaxValue, FileOptions.DeleteOnClose);

                //Get a reference to the parent of the Content Control which is where we will add the
                //chunk
                OpenXmlElement parent = ContentControl.Parent;

                //Get a unique reference for the Id of the chunk
                string altChunkId = "Component-" + ComponentFile.Item.ID.ToString();

                if (DocPart is MainDocumentPart)
                {
                    //Get a reference to the main document part of the target output file
                    MainDocumentPart MainDocPart = (MainDocumentPart)DocPart;

                    //Add the chuck with the specified Id
                    AlternativeFormatImportPart chunk =
                      MainDocPart.AddAlternativeFormatImportPart(
                          AlternativeFormatImportPartType.WordprocessingML, altChunkId);

                    //Feed the chunk with the file steam of the temporary file
                    chunk.FeedData(FS);

                    //Get the relationship Id of the chunk and insert it after the content control
                    //placeholder in the target output file
                    AltChunk altChunk = new AltChunk();
                    altChunk.Id = altChunkId;
                    parent.InsertAfter(altChunk, ContentControl);
                }
            }
            finally
            {
                //Closing down the temporary file steam also deletes the temporary file
                if (FS != null)
                    FS.Close();
            }
        }
    }
    
    //Load as a Hyperlink
    private void LoadHyperlink(OpenXmlPart DocPart, SdtElement ContentControl,
                                SPFile ComponentFile, LinkOptions LinkOption, string CustomLinkText)
    {

        string RawFileUrl = ComponentFile.Item.ParentList.ParentWeb.Url + "/" + ComponentFile.Url;
        string EncodedFileUrl = SPEncode.UrlEncodeAsUrl(RawFileUrl);
        string LinkText = string.Empty;

        Uri LinkUri = new Uri(EncodedFileUrl, UriKind.Absolute);

        OpenXmlElement parent = ContentControl.Parent;
        HyperlinkRelationship LinkRelationship = null;

        if (DocPart is MainDocumentPart)
        {
            MainDocumentPart MainDocPart = (MainDocumentPart)DocPart;
            LinkRelationship = MainDocPart.AddHyperlinkRelationship(LinkUri, true);
        }
        else if (DocPart is HeaderPart)
        {
            HeaderPart HeaderPart = (HeaderPart)DocPart;
            LinkRelationship = HeaderPart.AddHyperlinkRelationship(LinkUri, true);

        }
        else if (DocPart is FooterPart)
        {
            FooterPart FooterPart = (FooterPart)DocPart;
            LinkRelationship = FooterPart.AddHyperlinkRelationship(LinkUri, true);
        }

        switch (LinkOption)
        {
            case LinkOptions.Filename:
                {
                    LinkText = System.IO.Path.GetFileNameWithoutExtension(ComponentFile.Name);
                    break;
                }
            case LinkOptions.Url:
                {
                    LinkText = RawFileUrl;
                    break;
                }
            case LinkOptions.Title:
                {
                    try
                    {
                        LinkText = ComponentFile.Item["Title"].ToString();
                    }
                    catch { }
                    break;
                }
            case LinkOptions.Custom:
                {
                    LinkText = CustomLinkText;
                    break;
                }
        }

        if (LinkText == string.Empty)
            LinkText = RawFileUrl;

        DocumentFormat.OpenXml.Wordprocessing.Paragraph NewPara =
            GenerateHyperlinkParagraph(LinkRelationship.Id, LinkText);
        if (NewPara != null)
        {
            NewPara.ParagraphProperties = CloneParagraphProperties(ContentControl);
            parent.InsertAfter(NewPara, ContentControl);
        }

    }

    //Loads an image of specified MaxImageWidth, inserted within a hosting paragraph into the document.
    //Note that within a paragraph the image width is measured in English Metric Units (Emus)!
    private void LoadImage(OpenXmlPart DocPart, SdtElement ContentControl, SPFile ComponentFile, long MaxImageWidth)
    {
        ImagePart ImagePart = null;
        OpenXmlElement parent = ContentControl.Parent;
        DocumentFormat.OpenXml.Wordprocessing.Paragraph NewPara = null;

        if (DocPart is MainDocumentPart)
        {
            MainDocumentPart MainDocPart = (MainDocumentPart)DocPart;
            ImagePart = MainDocPart.AddImagePart(ImagePartType.Jpeg);
            ImagePart.FeedData(ComponentFile.OpenBinaryStream());
            string ImagePartId = MainDocPart.GetIdOfPart(ImagePart);
            NewPara = GenerateImageParagraph(ComponentFile, ImagePartId, MaxImageWidth);
        }
        else if (DocPart is HeaderPart)
        {
            HeaderPart HeaderPart = (HeaderPart)DocPart;
            ImagePart = HeaderPart.AddImagePart(ImagePartType.Jpeg);
            ImagePart.FeedData(ComponentFile.OpenBinaryStream());
            string ImagePartId = HeaderPart.GetIdOfPart(ImagePart);
            NewPara = GenerateImageParagraph(ComponentFile, ImagePartId, MaxImageWidth);
        }
        else if (DocPart is FooterPart)
        {
            FooterPart FooterPart = (FooterPart)DocPart;
            ImagePart = FooterPart.AddImagePart(ImagePartType.Jpeg);
            ImagePart.FeedData(ComponentFile.OpenBinaryStream());
            string ImagePartId = FooterPart.GetIdOfPart(ImagePart);
            NewPara = GenerateImageParagraph(ComponentFile, ImagePartId, MaxImageWidth);
        }


        if (NewPara != null)
        {
            NewPara.ParagraphProperties = CloneParagraphProperties(ContentControl);
            parent.InsertAfter(NewPara, ContentControl);
        }

    }

    //Overloaded call of the above but which calulates the MaxImageWidth based on the determining 
    //the width of the section in which the content control is hosted
    private void LoadImage(OpenXmlPart DocPart, SdtElement ContentControl, SPFile ComponentFile)
    {
        long MaxImageWidth;
        if (DocPart is MainDocumentPart)
        {
            MainDocumentPart MainDocPart = (MainDocumentPart)DocPart;
            MaxImageWidth = GetSectionWidth(ContentControl, MainDocPart.Document.Body);
        }
        else
            MaxImageWidth = cStandardPageWidth;
        LoadImage(DocPart, ContentControl, ComponentFile, MaxImageWidth);
    }

    //Loads the images in a sub image folder
    private void LoadSubFolderImages(OpenXmlPart DocPart, SdtElement ContentControl, SPFile ComponentFile,
                                        ImportOptions ImportOpts, ImageOptions ImageOpts)
    {
        SPFolder RootFolder = File.Item.ParentList.RootFolder;
        string SubFolder = ComponentFile.Item.ID.ToString();
        string SubFolderPath = Web.Url + "/" + RootFolder.Url + "/_w/" + SubFolder + "/";
        SPFolder _w;

        string SelectedSlides = string.Empty;
        string[] Selection = null;

        if (ImageOpts == ImageOptions.Selected)
            try
            { 
                SelectedSlides = ViewState[ComponentFile.Item.UniqueId.ToString()].ToString();
                Selection = SelectedSlides.Split(',');
            }
            catch { }

        if (Web.GetFolder(SubFolderPath).Exists)
        {
            _w = Web.GetFolder(SubFolderPath);
            System.Collections.SortedList ImageFiles = new System.Collections.SortedList();
            string ImageKey = string.Empty;

            foreach (SPFile ImageFile in _w.Files)
            {
                try
                {
                    if (ImageFile.Name.StartsWith("Slide "))
                        ImageKey = ImageFile.Name.Replace("Slide ", string.Empty).Replace(".jpg", string.Empty);
                    else if (ImageFile.Name.StartsWith("Sheet "))
                        ImageKey = "a" + ImageFile.Name.Replace("Sheet ", string.Empty).Replace(".jpg", string.Empty);
                    else if (ImageFile.Name.StartsWith("Chart "))
                        ImageKey = "b" + ImageFile.Name.Replace("Chart ", string.Empty).Replace(".jpg", string.Empty);
                    
                    ImageFiles.Add(ImageKey, ImageFile);
                }
                catch { }
            }
            
  //          for (int i = 0; i < ImageFiles.Count; i++)
            Boolean CanAdd;

            for (int i = ImageFiles.Count - 1; i > -1; i--)
            {
                SPFile ImageFile = (SPFile)ImageFiles.GetByIndex(i);
                if (ImageOpts == ImageOptions.All)
                    CanAdd = true;
                else if (Selection.Contains((i + 1).ToString()))
                    CanAdd = true;
                else
                    CanAdd = false;

                if (CanAdd)
                {

                    if ((ImportOpts == ImportOptions.LargeSlides) ||
                       (ImportOpts == ImportOptions.SheetsAndChartsAsLargeImages))
                        LoadImage(DocPart, ContentControl, ImageFile);
                    else if ((ImportOpts == ImportOptions.SmallSlides) ||
                            (ImportOpts == ImportOptions.SheetsAndChartsAsSmallImages))
                        LoadImage(DocPart, ContentControl, ImageFile, cStandardThumbWidth);
                }
            }
        }
    }

    //Loads a thumbnail image into the document by first retreiving the thumbnail image and then
    //calling LoadImage with a standard MaxImageWidth value
    private void LoadThumbnail(OpenXmlPart DocPart, SdtElement ContentControl, SPFile ComponentFile)
    {
        SPListItem ListItem = ComponentFile.Item;
        SPFolder RootFolder = ListItem.ParentList.RootFolder;
        string ThumbPath = Web.Url + "/" + RootFolder.Url + "/_w/" + ListItem.ID.ToString() + ".png";
        if (Web.GetFile(ThumbPath).Exists)
        {
            LoadImage(DocPart, ContentControl, Web.GetFile(ThumbPath), cStandardThumbWidth);
        }
    }

    #endregion

        #region Paragarph Generation Methods
        //The basis for these methods were generated using the document reflector tool which ships
        //with the SDK but have been modified to accept input parameters
    
    //Used to create a paragraph in which an object is embedded
    public DocumentFormat.OpenXml.Wordprocessing.Paragraph GenerateObjectParagraph(string ImagePartId, string ObjectPartId, string ProgId)
    {
        DocumentFormat.OpenXml.Wordprocessing.Paragraph paragraph1 =
            new DocumentFormat.OpenXml.Wordprocessing.Paragraph();// { RsidParagraphAddition = "0073502E", RsidRunAdditionDefault = "0073502E" };

        DocumentFormat.OpenXml.Wordprocessing.Run run1 =
            new DocumentFormat.OpenXml.Wordprocessing.Run();// { RsidRunProperties = "0073502E" };

        EmbeddedObject embeddedObject1 = new EmbeddedObject() { DxaOriginal = "3117", DyaOriginal = "890" };

        V.Shape shape1 = new V.Shape() { Id = "_x0000_i1026", Style = "width:80pt;height:60pt", Ole = false, Type = "#_x0000_t75" };
        V.ImageData imageData1 = new V.ImageData() { Title = "", RelationshipId = ImagePartId };

        shape1.Append(imageData1);
        Ovml.OleObject oleObject1 = new Ovml.OleObject() { Type = Ovml.OleValues.Embed, ProgId = ProgId, ShapeId = "_x0000_i1026", DrawAspect = Ovml.OleDrawAspectValues.Content, ObjectId = "_1322814911", Id = ObjectPartId };

        embeddedObject1.Append(shape1);
        embeddedObject1.Append(oleObject1);

        run1.Append(embeddedObject1);

        paragraph1.Append(run1);
        return paragraph1;
    }

    //Used to create a paragraph in which a Hyperlink is embedded
    public DocumentFormat.OpenXml.Wordprocessing.Paragraph GenerateHyperlinkParagraph(string LinkId, string LinkText)
    {
        DocumentFormat.OpenXml.Wordprocessing.Paragraph paragraph1 =
            new DocumentFormat.OpenXml.Wordprocessing.Paragraph(); 

        DocumentFormat.OpenXml.Wordprocessing.Hyperlink hyperlink1 =
            new DocumentFormat.OpenXml.Wordprocessing.Hyperlink() { History = true, Id = LinkId };

        DocumentFormat.OpenXml.Wordprocessing.Run run1 =
            new DocumentFormat.OpenXml.Wordprocessing.Run();

        DocumentFormat.OpenXml.Wordprocessing.RunProperties runProperties1 =
            new DocumentFormat.OpenXml.Wordprocessing.RunProperties();

        DocumentFormat.OpenXml.Wordprocessing.Color color1 =
            new DocumentFormat.OpenXml.Wordprocessing.Color() { Val = "0000FF", ThemeColor = ThemeColorValues.Hyperlink };
        
        DocumentFormat.OpenXml.Wordprocessing.Underline underline1 =
            new DocumentFormat.OpenXml.Wordprocessing.Underline() { Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single };

        runProperties1.Append(color1);
        runProperties1.Append(underline1);

        DocumentFormat.OpenXml.Wordprocessing.Text text1 =
            new DocumentFormat.OpenXml.Wordprocessing.Text();
        text1.Text = LinkText;

        run1.Append(runProperties1);
        run1.Append(text1);

        hyperlink1.Append(run1);

        paragraph1.Append(hyperlink1);
        return paragraph1;
    }

    //Used to create a paragraph in which an Image is embedded
    public static DocumentFormat.OpenXml.Wordprocessing.Paragraph GenerateImageParagraph(SPFile ImageFile, string ImagePartId, long MaxImageWidth)
    {
        string ImageFileName = ImageFile.Name;
        Bitmap bm = new Bitmap(ImageFile.OpenBinaryStream());
        Int64 wEmu = (long)bm.Width * (long)((float)914400 / bm.HorizontalResolution);
        Int64 hEmu = (long)bm.Height * (long)((float)914400 / bm.VerticalResolution);
        float ScaleFactor = (float)MaxImageWidth / (float)wEmu;

        if (wEmu > MaxImageWidth)
        {
            wEmu = (long)(wEmu * ScaleFactor);
            hEmu = (long)(hEmu * ScaleFactor);
        }

        string ImageFieldNameLessExtension = System.IO.Path.GetFileNameWithoutExtension(ImageFileName);
        var element =
            new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
                 new DocumentFormat.OpenXml.Wordprocessing.Run(
                    new DocumentFormat.OpenXml.Wordprocessing.Drawing(
                        new Inline(
                            new Extent() { Cx = wEmu, Cy = hEmu },
                            new DocProperties() { Id = (UInt32Value)1U, Name = ImageFieldNameLessExtension, Description = ImageFileName },
                            new DocumentFormat.OpenXml.Drawing.Wordprocessing.NonVisualGraphicFrameDrawingProperties(
                                new GraphicFrameLocks() { NoChangeAspect = true }),
                            new Graphic(
                                new GraphicData(
                                    new DocumentFormat.OpenXml.Drawing.Pictures.Picture(
                                        new DocumentFormat.OpenXml.Drawing.Pictures.NonVisualPictureProperties(
                                            new DocumentFormat.OpenXml.Drawing.Pictures.NonVisualDrawingProperties() { Id = (UInt32Value)0U, Name = ImageFileName },
                                            new DocumentFormat.OpenXml.Drawing.Pictures.NonVisualPictureDrawingProperties()),
                                        new DocumentFormat.OpenXml.Drawing.Pictures.BlipFill(
                                            new Blip() { Embed = ImagePartId, CompressionState = BlipCompressionValues.Print },
                                            new Stretch(
                                                new FillRectangle())),
                                        new DocumentFormat.OpenXml.Drawing.Pictures.ShapeProperties(
                                            new Transform2D(
                                                new Offset() { X = 0L, Y = 0L },
                                                new Extents() { Cx = wEmu, Cy = hEmu }),
                                            new PresetGeometry(
                                                new AdjustValueList()
                                            ) { Preset = ShapeTypeValues.Rectangle })
                                        )
                                ) { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" })
                        ) { DistanceFromTop = (UInt32Value)0U, DistanceFromBottom = (UInt32Value)0U, DistanceFromLeft = (UInt32Value)0U, DistanceFromRight = (UInt32Value)0U }))
            );
        return element;
    }

    #endregion

        #region Support and Helper Methods

    //Depending on the file extension of the compnent file we need to embed the object either as a
    //Package (new formats) or as an Object (legacy formats).  This method tells us which.
    private ImportObjectAs ObjectOrPackage(string Ext)
    {
        switch (Ext.ToLower())
        {
            case ".docx":
            case ".dotx":
            case ".docm":
            case ".dotm":
            case ".xlsx":
            case ".xltx":
            case ".xlsm":
            case ".xltm":
            case ".pptx":
            case ".potx":
            case ".pptm":
            case ".potm":
            case ".ppsx":
                return ImportObjectAs.Package;
            default:
                return ImportObjectAs.Object;
        }
    }

    //Reads from the Registry the Program Id and Content Id for the file extension (Ext)
    //(I started out storing this identifiers as constants but rapidly came to the conclusion
    //that that was silly)
    private void SetProgAndContentTypeId(string Ext, ref string ProgId, ref string ContentTypeId)
    {
        RegistryKey BaseKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.ClassesRoot, string.Empty);
        RegistryKey SubKey = BaseKey.OpenSubKey(Ext);
        ProgId = SubKey.GetValue(string.Empty).ToString();
        ContentTypeId = SubKey.GetValue("Content Type").ToString();
        SubKey.Close();
        BaseKey.Close();
    }

    //Returns a clone of the ParagraphProperties which are applied to the paragraph in which the 
    //Content Control resides
    private DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties CloneParagraphProperties(SdtElement ContentControl)
    {
        DocumentFormat.OpenXml.Wordprocessing.Paragraph Para = null;
        DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties SourceProperties = null;
        try
        {
            if (ContentControl is SdtBlock)
            {
                SdtBlock Block = (SdtBlock)ContentControl;
                SdtContentBlock ContentBlock = Block.SdtContentBlock;
                Para = ContentBlock.GetFirstChild<DocumentFormat.OpenXml.Wordprocessing.Paragraph>();
            }
            else if (ContentControl is SdtCell)
            {
                SdtCell Cell = (SdtCell)ContentControl;
                SdtContentCell ContentCell = Cell.SdtContentCell;
                DocumentFormat.OpenXml.Wordprocessing.TableCell TableCell =
                    ContentCell.GetFirstChild<DocumentFormat.OpenXml.Wordprocessing.TableCell>();
                Para = TableCell.GetFirstChild<DocumentFormat.OpenXml.Wordprocessing.Paragraph>();
            }
            SourceProperties = Para.GetFirstChild<DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties>();
            return (DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties)SourceProperties.Clone();
        }
        catch { }
        return null;
    }

    //Returns the width of the document section in which the content control is hosted
    private long GetSectionWidth(SdtElement ContentControl, Body MainDocBody)
    {
        long Result = cStandardPageWidth;
        try
        {
            List<OpenXmlElement> Paras =
                ContentControl.ElementsAfter().Where(
                   pPr => (pPr is DocumentFormat.OpenXml.Wordprocessing.Paragraph)).ToList();
            foreach (OpenXmlElement Element in Paras)
            {
                DocumentFormat.OpenXml.Wordprocessing.Paragraph para = (DocumentFormat.OpenXml.Wordprocessing.Paragraph)Element;
                DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties PP = para.GetFirstChild<DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties>();
                if (PP != null)
                {
                    SectionProperties SP = PP.GetFirstChild<SectionProperties>();
                    if (SP != null)
                    {
                        PageSize PS = SP.GetFirstChild<PageSize>();
                        PageMargin PM = SP.GetFirstChild<PageMargin>();
                        float Inches = ((float)(PS.Width - PM.Left - PM.Right - PM.Gutter) / 1440);
                        return (long)(Inches * 914400);
                    }
                }
            }


            SectionProperties DefaultSectionProperties = MainDocBody.GetFirstChild<SectionProperties>();
            if (DefaultSectionProperties != null)
            {
                PageSize PS = DefaultSectionProperties.GetFirstChild<PageSize>();
                PageMargin PM = DefaultSectionProperties.GetFirstChild<PageMargin>();
                float Inches = ((float)(PS.Width - PM.Left - PM.Right - PM.Gutter) / 1440);
                return (long)(Inches * 914400);
            }
        }
        catch { }

        return Result;

    }

    //Creates a TableCell based on the code returned by Document Reflector
    //I'm not totally satisfied here because Content Controls in table cells are a rather different
    //beast to standard content controls. TODO: This needs some more work
    public static DocumentFormat.OpenXml.Wordprocessing.TableCell GenerateTableCell()
    {
        var element =
            new DocumentFormat.OpenXml.Wordprocessing.TableCell(
                new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
                    new TableCellWidth() { Width = "2943", Type = TableWidthUnitValues.Dxa }),

                new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
                    new DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties(
                        new ParagraphStyleId() { Val = "Normal" },
                        new OutlineLevel() { Val = 0 })
                ));
        return element;
    }

    #endregion


    #endregion

}
